{"openapi":"3.1.0","info":{"title":"InboxSpec API","version":"0.1.0","description":"Email infrastructure for automated tests and browser agents: real inboxes that receive real mail, and a blocking wait that returns the moment a message matches.\n\nRead this before your first call:\n\n- **Wait, do not poll.** POST /v1/messages/wait blocks until a message matches. It is never rate limited and never metered, and it returns in one round trip if the message already arrived. A polling loop is slower, costs more requests, and is the thing this API exists to replace.\n- **An empty result means one thing only:** the inbox exists, quota is available, and nothing matched. Every other condition is a typed error with a code from the ErrorCode enum. You never have to guess why you got nothing.\n- **A timeout is a result, not an error.** It carries non_matching_arrivals and a hint that names the predicate most likely to be at fault.\n- **Bodies are truncated by default** with a url to the whole part, so a response costs a predictable number of tokens.\n- **Addresses are deterministic.** POST /v1/inboxes/for-run maps a run identifier to one stable address forever, so parallel test steps need no coordination.\n\n## Getting a key\n\nEvery route below except the ones tagged `meta` needs `Authorization: Bearer is_live_…` or `is_test_…`. Keys are issued in the console: create an account, create a project, and create a key on it.\n\n**A key is shown once and stored as a SHA-256 of itself.** No route here or anywhere else will return one again, so a lost key is replaced rather than recovered. The same is true of the SMTP password below.\n\nThe routes that issue those credentials are not in this document. That is deliberate rather than an omission: this description is read by the agents and clients that authenticate *with* the credentials, and a surface that mints them does not belong in the same reference. They are the console's own API, not part of the product surface.\n\n## Receiving mail\n\nInboxes on the shared domain are addressed `<project_key>.<tag>@inbox.inboxspec.com`, and `POST /v1/inboxes/for-run` derives one from a run identifier for you.\n\n**The shared domain cannot be used to sign up to your own product.** It is on the public disposable-email blocklists, as every shared testing domain is, so your own registration form will refuse it. Add a domain you control with `POST /v1/domains`, publish the DNS records it hands back, and call `POST /v1/domains/{id}/verify`. That is in the base tier.\n\n## Sending mail at us, from your staging environment\n\nPoint your application's normal SMTP transport at the capture relay on `capture.inboxspec.com`, authenticating with the SMTP credential from the console. It accepts every message, files it into your project, and **never delivers** — there is no outbound path in the service to misconfigure, which is what `GET /.well-known/inboxspec-abuse` asserts. Staging stops burning your sending reputation on addresses that were never real.\n\nCaptured mail is filed under a tag computed from the original recipient, `capture.<domain>.<local-part>`, with the full original address on `labels.recipient` — so you can find it from the recipient alone. `GET /v1/inboxes?tag_prefix=capture` lists it. It meters as `message.captured`, separately from `message.received`.\n\nThe relay is SMTP, so it appears nowhere in the routes below.\n\n## Rehearsing a delivery failure\n\nReserved `sim.*` addresses on the receiving edge behave as a named failure would, at the wire level, without you having to find a mailbox that is genuinely broken: `sim.hard-bounce` answers 550 5.1.1, `sim.soft-bounce` 452 4.2.2, `sim.oversize` 552 5.3.4, and `sim.greylist` answers 451 4.7.1 and then accepts on a later attempt — which is how you prove your retry actually retries. `sim.delay` accepts immediately and stays invisible to reads for a few seconds, so a wait against it genuinely blocks. `sim.complaint` stores a readable message and raises a complaint event.","contact":{"name":"InboxSpec abuse and support","email":"abuse@inboxspec.com"}},"servers":[{"url":"https://api.inboxspec.com","description":"us region"}],"tags":[{"name":"fixtures","description":"Idempotent run setup and explicit readiness outcomes."},{"name":"authentication","description":"Validate project credentials and expected context."},{"name":"messages","description":"Reading mail, and waiting for it to arrive."},{"name":"inboxes","description":"Addresses that receive mail."},{"name":"domains","description":"Your own receiving domains, and proving you own them."},{"name":"usage","description":"What this project has consumed."},{"name":"webhooks","description":"Where events are delivered, and the log of every attempt."},{"name":"data-protection","description":"Erasure, the audit trail, retention and where the data lives."},{"name":"meta","description":"Unauthenticated endpoints."}],"security":[{"bearerAuth":[]}],"paths":{"/health":{"get":{"operationId":"health","summary":"Liveness","description":"Unauthenticated. Touches no database and no object store, so it reports whether this process is serving, not whether its dependencies are.","tags":["meta"],"security":[],"responses":{"200":{"description":"The process is serving.","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","const":"ok"},"brand":{"type":"string"},"region":{"type":"string","enum":["us","eu"]},"time":{"type":"string"}},"required":["status","brand","region","time"],"additionalProperties":false}}}}}}},"/openapi.json":{"get":{"operationId":"openapi","summary":"This document","description":"Unauthenticated. The machine-readable description of every route.","tags":["meta"],"security":[],"responses":{"200":{"description":"An OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/docs":{"get":{"operationId":"docs","summary":"This document, rendered","description":"Unauthenticated. The same OpenAPI document as an interactive reference, with a request client. Send a person here and a program to /openapi.json.","tags":["meta"],"security":[],"responses":{"200":{"description":"The reference page.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/stream/v1/inboxes/{id}/events":{"get":{"operationId":"streamInboxEvents","summary":"Watch an inbox, as Server-Sent Events","description":"Holds the connection open and pushes one `message` event per arrival, fed by the same Postgres NOTIFY that wakes a blocking wait. Not a poll: it costs one query per arrival and nothing in between.\n\nAuthenticates two ways, and only two. Anything that can set a header should send `Authorization: Bearer is_…` — that is the ordinary path. A browser cannot, because EventSource has no header API, so it sends `?token=` from POST /v1/inboxes/{id}/stream-token: a credential scoped to this one inbox that expires in minutes, rather than an API key in a URL that access logs would keep forever.\n\nEvents are `open`, `message`, `closed` and `error`, plus comment heartbeats that no handler sees. Every `message` frame carries an `id`, so a dropped connection resumes exactly where it stopped via `Last-Event-ID` — EventSource sends it automatically. A `message` carries the envelope, the subject, the extracted codes and links, and no body: fetch that with GET /v1/messages/{id} when you want it.\n\nThe stream closes with a stated reason rather than going silent: `token_expired`, `max_duration`, `slow_consumer` or `read_failed`. Reconnecting is the designed response to all four.","tags":["messages"],"security":[{"bearerAuth":[]}],"x-required-scope":"messages:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The inbox id. Must be the inbox the token was minted for."},{"name":"token","in":"query","required":false,"description":"A stream token from POST /v1/inboxes/{id}/stream-token. Send this or an Authorization header, not both — a header always wins.","schema":{"type":"string"}},{"name":"last_event_id","in":"query","required":false,"description":"Resume point, for a client hand-rolling its own reconnect. A browser sends the Last-Event-ID header instead and needs neither. Anything malformed starts a fresh stream rather than failing.","schema":{"type":"string"}}],"responses":{"200":{"description":"The stream. Open until it says otherwise.","content":{"text/event-stream":{"schema":{"type":"string"}}}},"401":{"description":"unauthenticated: no credential, an expired token, or a token minted for a different inbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: this project's read allowance is spent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"inbox_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/.well-known/inboxspec-abuse":{"get":{"operationId":"abuseReport","summary":"Abuse contact and takedown path","description":"Unauthenticated, and deliberately so: an abuse desk chasing a report has no account here, and a contact they cannot reach is not a contact. Structured rather than prose so it can be read by the automation an abuse desk actually runs.","tags":["meta"],"security":[],"responses":{"200":{"description":"The published contact.","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"brand":{"type":"string"},"contact":{"type":"string"},"region":{"type":"string","enum":["us","eu"]},"service":{"type":"string"},"outbound_delivery":{"type":"string","const":"none","description":"There is no path in this service that delivers mail to a third party, so it cannot be the source of anything you received."},"controls":{"type":"array","items":{"type":"string"}},"takedown":{"type":"object","properties":{"contact":{"type":"string"},"acknowledgement_hours":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"action_hours":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"include":{"type":"array","items":{"type":"string"}}},"required":["contact","acknowledgement_hours","action_hours","include"],"additionalProperties":false},"receiving":{"type":"object","properties":{"mx_host":{"type":"string"},"shared_inbox_domain":{"type":"string"},"capture_relay_host":{"type":"string"}},"required":["mx_host","shared_inbox_domain","capture_relay_host"],"additionalProperties":false}},"required":["brand","contact","region","service","outbound_delivery","controls","takedown","receiving"],"additionalProperties":false,"description":"Unauthenticated. Served at /.well-known/inboxspec-abuse so an abuse desk can find the contact and the takedown path without an account."}}}}}}},"/v1/fixtures/ensure":{"post":{"operationId":"ensureFixtures","summary":"Ensure run fixtures","description":"Validates the whole manifest and expected context before writes. Reuses matching inboxes and refreshes leases without clearing messages. Definition drift and partial failures are explicit outcomes. Ready means authentication, permissions and provisioning, not delivery.","tags":["fixtures"],"security":[{"bearerAuth":[]}],"x-required-scope":"inboxes:write","x-rate-limit-class":"write","requestBody":{"description":"Versioned manifest and run context.","required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"run_id":{"type":"string","minLength":1,"maxLength":96,"pattern":"^[a-z0-9_-]+(?:\\.[a-z0-9_-]+)*$"},"manifest":{"type":"object","properties":{"version":{"type":"number","const":1},"fixtures":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":32,"pattern":"^[a-z0-9_-]+$"},"domain":{"type":"string","minLength":1,"maxLength":253},"retention_seconds":{"type":"integer","minimum":60,"maximum":2592000,"description":"Seconds until the inbox and its messages are hard-deleted."},"labels":{"type":"object","propertyNames":{"type":"string","maxLength":64},"additionalProperties":{"type":"string","maxLength":256}}},"required":["name","retention_seconds"],"additionalProperties":false}}},"required":["version","fixtures"],"additionalProperties":false},"project_id":{"type":"string","minLength":1,"maxLength":128},"environment":{"type":"string","enum":["local","staging","production"]},"scopes":{"maxItems":10,"type":"array","items":{"type":"string","enum":["inboxes:read","inboxes:write","messages:read","messages:wait","messages:delete","domains:read","domains:write","webhooks:read","webhooks:write","usage:read"]}}},"required":["run_id","manifest"],"additionalProperties":false}}}},"responses":{"200":{"description":"Inspect status and every fixture; partial/failed results are not readiness.","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","enum":["ready","partial","failed"]},"run_id":{"type":"string"},"context":{"type":"object","properties":{"status":{"type":"string","const":"authenticated"},"key":{"type":"object","properties":{"id":{"type":"string"},"environment":{"anyOf":[{"type":"string","enum":["live","test"]},{"type":"null"}]},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","environment","expires_at"],"additionalProperties":false},"project":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"}},"required":["id","key"],"additionalProperties":false},"scopes":{"type":"array","items":{"type":"string","enum":["inboxes:read","inboxes:write","messages:read","messages:wait","messages:delete","domains:read","domains:write","webhooks:read","webhooks:write","usage:read"]}},"server":{"type":"object","properties":{"environment":{"type":"string","enum":["local","staging","production","unknown"]},"region":{"type":"string","enum":["us","eu"]},"api_origin":{"type":"string"}},"required":["environment","region","api_origin"],"additionalProperties":false}},"required":["status","key","project","scopes","server"],"additionalProperties":false},"checks":{"type":"object","properties":{"authentication":{"type":"string","const":"passed"},"permissions":{"type":"string","const":"passed"},"provisioning":{"type":"string","enum":["passed","failed"]},"admission":{"type":"string","const":"unknown"},"delivery":{"type":"string","const":"not_checked"}},"required":["authentication","permissions","provisioning","admission","delivery"],"additionalProperties":false},"cleanup":{"type":"object","properties":{"tag_prefix":{"type":"string"},"label_key":{"type":"string"},"label_value":{"type":"string"}},"required":["tag_prefix","label_key","label_value"],"additionalProperties":false},"fixtures":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"status":{"type":"string","enum":["ready","failed","not_attempted"]},"inbox":{"type":"object","properties":{"id":{"type":"string"},"address":{"type":"string"},"tag":{"type":"string","description":"Local part below the project namespace, dot-nested."},"labels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"retention_seconds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Message retention in seconds, not inbox lifetime."},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Legacy field; null because inboxes remain until explicitly deleted."},"created_at":{"type":"string"},"last_message_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"message_count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","address","tag","labels","retention_seconds","expires_at","created_at","last_message_at","message_count"],"additionalProperties":false},"error":{"type":"object","properties":{"code":{"type":"string","enum":["unauthenticated","invalid_api_key","insufficient_scope","forbidden","inbox_not_found","message_not_found","attachment_not_found","domain_not_found","domain_not_verified","project_not_found","webhook_not_found","article_not_found","invalid_request","unsupported_format","payload_too_large","conflict","quota_exceeded","organisation_limit_exceeded","rate_limited","concurrency_limit","blocked_recipient","abuse_suspended","ingest_ceiling_exceeded","storage_unavailable","internal"],"description":"Stable machine-readable failure code. Branch on this, never on the HTTP status or the message text."},"reason":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","reason","retryable"],"additionalProperties":false}},"required":["name","status"],"additionalProperties":false}}},"required":["status","run_id","context","checks","cleanup","fixtures"],"additionalProperties":false}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Receiving domain not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Receiving domain not verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/check":{"get":{"operationId":"checkAuth","summary":"Check this key and expected context","description":"Returns only the authenticated key and project metadata. Optional project, deployment environment and scope expectations fail explicitly. No inbox or message access; never gated by message quota. Authentication does not prove email delivery. Test/live key labels do not isolate data.","tags":["authentication"],"security":[{"bearerAuth":[]}],"x-required-scope":null,"x-rate-limit-class":"read","parameters":[{"name":"project_id","in":"query","required":false,"description":"project_id","schema":{"type":"string","minLength":1,"maxLength":128}},{"name":"environment","in":"query","required":false,"description":"environment","schema":{"type":"string","enum":["local","staging","production"]}},{"name":"scopes","in":"query","required":false,"description":"Comma-separated required permissions. Checking them grants nothing.","schema":{"type":"string","maxLength":1024,"description":"Comma-separated required permissions. Checking them grants nothing."}}],"responses":{"200":{"description":"Authenticated metadata; no delivery claim.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthCheck"}}}},"400":{"description":"Invalid expectation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, malformed, unknown or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Context mismatch, missing scope or suspended organisation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Read rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal service failure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/inboxes/for-run":{"post":{"operationId":"inboxForRun","summary":"Get the deterministic inbox for a run","description":"Maps a run identifier to one stable address, forever. Safe to call concurrently from every step of a test: the same run_id always yields the same inbox, so no step needs to pass an address to another. run_id must fit the tag grammar (a-z, 0-9, hyphen, underscore, dots between levels); an unruly value is rejected rather than slugified, because two ids that slugified alike would share one inbox.","tags":["inboxes"],"security":[{"bearerAuth":[]}],"x-required-scope":"inboxes:write","x-rate-limit-class":"write","requestBody":{"description":"The run to address.","required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"run_id":{"type":"string","minLength":1,"description":"Stable identifier for the run — a CI run number, job id or UUID. The same value always maps to the same address, forever. Same grammar as tag: a-z, 0-9, hyphen, underscore, dots between levels, so \"CI Run #4182\" is refused; pass \"ci-run-4182\" or the raw run number instead."},"domain":{"type":"string"},"labels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Free-form grouping, used by inboxes.cleanup."},"retention_seconds":{"type":"integer","minimum":60,"maximum":2592000,"description":"Seconds until the inbox and its messages are hard-deleted."}},"required":["run_id"],"additionalProperties":false}}}},"responses":{"200":{"description":"The inbox for this run_id. The same run_id always returns the same address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Inbox"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"domain_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"domain_not_verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/inboxes/cleanup":{"post":{"operationId":"cleanupInboxes","summary":"Bulk-delete inboxes by tag prefix or label","description":"Deletes matching inboxes, purges their messages and removes the stored objects. Requires at least one selector.","tags":["inboxes"],"security":[{"bearerAuth":[]}],"x-required-scope":"inboxes:write","x-rate-limit-class":"write","requestBody":{"description":"Which inboxes to delete.","required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"tag_prefix":{"type":"string","description":"Matches the tag and its children, e.g. \"ci.pr-4182\"."},"label_key":{"type":"string"},"label_value":{"type":"string"}},"additionalProperties":false,"description":"At least one selector is required. A body with no selector is rejected rather than deleting everything."}}}},"responses":{"200":{"description":"Counts of what was removed. Deletes up to 200 inboxes per call; repeat until inboxes_deleted is 0.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CleanupResult"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/inboxes":{"post":{"operationId":"createInbox","summary":"Create an inbox","description":"Creates an inbox on the shared domain, or on one of this project's verified domains. Idempotent on the resulting address: creating the same tag twice returns the same inbox. Message retention does not expire the inbox; remove it explicitly when finished.","tags":["inboxes"],"security":[{"bearerAuth":[]}],"x-required-scope":"inboxes:write","x-rate-limit-class":"write","requestBody":{"description":"Inbox to create.","required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"tag":{"type":"string","minLength":1,"description":"Dot-nested local part, e.g. \"ci.pr-4182.signup\". Lowercased on write. Each dot-separated segment must be a-z, 0-9, hyphen or underscore; anything else is rejected rather than slugified, because two tags that slugified alike would share one inbox."},"domain":{"type":"string","description":"A verified receiving domain of this project. Omit to use the shared domain."},"labels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Free-form grouping, used by inboxes.cleanup."},"retention_seconds":{"type":"integer","minimum":60,"maximum":2592000,"description":"Seconds until the inbox and its messages are hard-deleted."}},"required":["tag"],"additionalProperties":false}}}},"responses":{"201":{"description":"The inbox. Creating the same tag twice is idempotent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Inbox"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"domain_not_found: no such domain on this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"domain_not_verified: publish the DNS records first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listInboxes","summary":"List inboxes","description":"Newest first. Filter by dot-nested tag prefix or by a label key and value.","tags":["inboxes"],"security":[{"bearerAuth":[]}],"x-required-scope":"inboxes:read","x-rate-limit-class":"read","parameters":[{"name":"tag_prefix","in":"query","required":false,"description":"Dot-nested prefix. \"ci.pr-4182\" matches \"ci.pr-4182\" and \"ci.pr-4182.signup\".","schema":{"type":"string"}},{"name":"label_key","in":"query","required":false,"description":"Label key. Send with label_value.","schema":{"type":"string"}},{"name":"label_value","in":"query","required":false,"description":"Label value. Send with label_key.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size. Defaults to 50, capped at 200.","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"next_cursor from the previous page. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxList"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/inboxes/{id}/stream-token":{"post":{"operationId":"createInboxStreamToken","summary":"Mint a short-lived token for this inbox's event stream","description":"Returns a token good for a few minutes that opens GET /stream/v1/inboxes/{id}/events, and nothing else. It exists because EventSource cannot set an Authorization header: a token in a query string is kept by access logs and Referer headers, so this one is scoped to one inbox and expires, rather than being an API key that is scoped to a project and does not. Anything that can send headers should skip this and pass its API key to the stream directly.","tags":["inboxes"],"security":[{"bearerAuth":[]}],"x-required-scope":"messages:read","x-rate-limit-class":"write","responses":{"201":{"description":"The token, its expiry, and the ready-made stream URL. Shown once, and good only for this one inbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StreamToken"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"inbox_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/inboxes/{id}":{"get":{"operationId":"getInbox","summary":"Get one inbox","description":"Accepts an inbox id or the inbox address.","tags":["inboxes"],"security":[{"bearerAuth":[]}],"x-required-scope":"inboxes:read","x-rate-limit-class":"read","parameters":[{"name":"id","in":"path","required":true,"description":"Inbox id (ibx_…) or the inbox address.","schema":{"type":"string"}}],"responses":{"200":{"description":"The inbox.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Inbox"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"inbox_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteInbox","summary":"Delete an inbox and purge its messages","description":"Hard delete. Message rows and their stored bodies and attachments go with it; this is not recoverable.","tags":["inboxes"],"security":[{"bearerAuth":[]}],"x-required-scope":"inboxes:write","x-rate-limit-class":"write","parameters":[{"name":"id","in":"path","required":true,"description":"Inbox id (ibx_…) or the inbox address.","schema":{"type":"string"}}],"responses":{"200":{"description":"Counts of what was removed with it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxDeleted"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"inbox_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/wait":{"post":{"operationId":"waitForMessage","summary":"Block until a message matches","description":"Returns as soon as a message satisfies the match, or on timeout. A timeout is a successful response carrying the number of non-matching arrivals and a specific hint; a missing inbox, an exhausted quota and a rate limit are all distinct typed errors instead.","tags":["messages"],"security":[{"bearerAuth":[]}],"x-required-scope":"messages:wait","x-rate-limit-class":"exempt","requestBody":{"description":"What to wait for.","required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"inbox":{"type":"string","description":"Inbox id or address. Must already exist."},"match":{"type":"object","properties":{"subject_contains":{"type":"string","description":"Substring of the whole subject line, punctuation included."},"from_contains":{"type":"string","description":"Substring of the From address and display name, searched together."},"to_contains":{"type":"string","description":"Substring of this message's recipients. A delivery that arrived by BCC carries no To entry for the inbox."},"body_contains":{"type":"string","description":"Substring of the subject and the plain-text part. Never the HTML, so an HTML-only message matches on its subject alone."},"correlation_id":{"description":"Exact case-sensitive X-InboxSpec-Correlation-ID header value.","type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"},"header_contains":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Header name to a substring its value must contain."},"since":{"type":"string","description":"ISO 8601 lower bound on received_at."},"has_code":{"type":"boolean"},"has_link_kind":{"type":"string","enum":["verify","sign_in","unsubscribe","reset_password","other"]},"message_id":{"type":"string","description":"Exact stored RFC 5322 Message-ID. Matching is non-consuming, without durable response replay."}},"additionalProperties":false,"description":"Omit to take the next message that arrives."},"timeout_ms":{"type":"integer","minimum":1,"maximum":285000,"description":"Defaults to 120000. Above 285000 is rejected, never clamped."},"format":{"type":"array","items":{"type":"string","enum":["text","html","raw","headers"]}},"preview_chars":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Characters of body text before truncation. Defaults to 2000, clamped at 100000 rather than refused."}},"required":["inbox"],"additionalProperties":false,"description":"Blocks until a message matches. Never rate limited and never metered. A timeout is a result, not an error: it carries a count of non-matching arrivals and a specific hint."}}}},"responses":{"200":{"description":"Matched, or timed out with a diagnosis.","content":{"application/json":{"schema":{"description":"Exactly one of two outcomes. `matched` carries the message; `timeout` is a successful result meaning the inbox exists, quota was available, and nothing matched in time.","oneOf":[{"type":"object","required":["status","message","waited_ms"],"properties":{"status":{"const":"matched"},"message":{"$ref":"#/components/schemas/MessageView"},"waited_ms":{"type":"integer"}}},{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","const":"timeout"},"request_id":{"type":"string"},"started_at":{"type":"string"},"completed_at":{"type":"string"},"reason":{"type":"string","enum":["no_arrivals","no_matching_arrivals"]},"waited_ms":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"inbox":{"type":"string"},"non_matching_arrivals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Messages that arrived during the wait but failed the match."},"hint":{"type":"string","description":"The most likely reason and what to change, written for a model."}},"required":["status","waited_ms","inbox","non_matching_arrivals","hint"],"additionalProperties":false}]}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"organisation_limit_exceeded when no stored message matches and workspace message admission is blocked. Message allowance errors include details.limit_kind, used, limit, plan_id and period_resets_at. Existing matches remain readable. With organisation limits disabled, the legacy quota_exceeded error applies.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"inbox_not_found. A missing inbox is never reported as a timeout — that distinction is the point of this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages":{"get":{"operationId":"listMessages","summary":"List messages","description":"Newest first, across the project or one inbox. Bodies are not inlined: every body slice carries a url instead, so a page of results costs a predictable number of tokens.","tags":["messages"],"security":[{"bearerAuth":[]}],"x-required-scope":"messages:read","x-rate-limit-class":"read","parameters":[{"name":"inbox","in":"query","required":false,"description":"Inbox id or address. Omit to list across the whole project.","schema":{"type":"string"}},{"name":"subject_contains","in":"query","required":false,"description":"Substring of the whole subject line, punctuation included.","schema":{"type":"string","description":"Substring of the whole subject line, punctuation included."}},{"name":"from_contains","in":"query","required":false,"description":"Substring of the From address and display name, searched together.","schema":{"type":"string","description":"Substring of the From address and display name, searched together."}},{"name":"to_contains","in":"query","required":false,"description":"Substring of this message's recipients. A delivery that arrived by BCC carries no To entry for the inbox.","schema":{"type":"string","description":"Substring of this message's recipients. A delivery that arrived by BCC carries no To entry for the inbox."}},{"name":"body_contains","in":"query","required":false,"description":"Substring of the subject and the plain-text part. Never the HTML, so an HTML-only message matches on its subject alone.","schema":{"type":"string","description":"Substring of the subject and the plain-text part. Never the HTML, so an HTML-only message matches on its subject alone."}},{"name":"correlation_id","in":"query","required":false,"description":"Exact case-sensitive X-InboxSpec-Correlation-ID header value.","schema":{"description":"Exact case-sensitive X-InboxSpec-Correlation-ID header value.","type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"}},{"name":"header_contains","in":"query","required":false,"description":"Header name to a substring its value must contain.","schema":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Header name to a substring its value must contain."},"style":"deepObject","explode":true},{"name":"since","in":"query","required":false,"description":"ISO 8601 lower bound on received_at.","schema":{"type":"string","description":"ISO 8601 lower bound on received_at."}},{"name":"has_code","in":"query","required":false,"description":"Case-insensitive substring. All match fields are ANDed; absent fields do not constrain.","schema":{"type":"boolean"}},{"name":"has_link_kind","in":"query","required":false,"description":"Case-insensitive substring. All match fields are ANDed; absent fields do not constrain.","schema":{"type":"string","enum":["verify","sign_in","unsubscribe","reset_password","other"]}},{"name":"message_id","in":"query","required":false,"description":"Exact stored RFC 5322 Message-ID. Matching is non-consuming, without durable response replay.","schema":{"type":"string","description":"Exact stored RFC 5322 Message-ID. Matching is non-consuming, without durable response replay."}},{"name":"limit","in":"query","required":false,"description":"Page size. Defaults to 50, capped at 200.","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"next_cursor from the previous page. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Newest first. Body previews are empty here by design; use the url on each body slice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageList"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"inbox_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/{id}/body":{"get":{"operationId":"getMessageBody","summary":"Fetch a whole message body","description":"The untruncated part, as bytes. This is the url that every truncated body slice points at.","tags":["messages"],"security":[{"bearerAuth":[]}],"x-required-scope":"messages:read","x-rate-limit-class":"read","parameters":[{"name":"id","in":"path","required":true,"description":"Message id (msg_…).","schema":{"type":"string"}},{"name":"format","in":"query","required":true,"description":"Which part to return.","schema":{"type":"string","enum":["text","html","raw"]}}],"responses":{"200":{"description":"The whole part, untruncated.","content":{"text/plain":{"schema":{"type":"string"}},"text/html":{"schema":{"type":"string"}},"message/rfc822":{"schema":{"type":"string"}}}},"400":{"description":"unsupported_format: this message has no such part. The error lists the parts it does have.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"message_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/{id}/attachments/{attachmentId}":{"get":{"operationId":"downloadAttachment","summary":"Download an attachment","description":"Redirects to a short-lived presigned object-store URL. Attachment bytes never pass through the API origin.","tags":["messages"],"security":[{"bearerAuth":[]}],"x-required-scope":"messages:read","x-rate-limit-class":"read","parameters":[{"name":"id","in":"path","required":true,"description":"Message id (msg_…).","schema":{"type":"string"}},{"name":"attachmentId","in":"path","required":true,"description":"Attachment id (att_…), as listed on the message.","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to a short-lived presigned URL. Follow it to get the bytes.","headers":{"Location":{"schema":{"type":"string"},"description":"The presigned URL."}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"message_not_found or attachment_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages/{id}":{"get":{"operationId":"getMessage","summary":"Get one message","description":"Includes the truncated text body. Ask for html, raw or headers with the format parameter.","tags":["messages"],"security":[{"bearerAuth":[]}],"x-required-scope":"messages:read","x-rate-limit-class":"read","parameters":[{"name":"id","in":"path","required":true,"description":"Message id (msg_…).","schema":{"type":"string"}},{"name":"format","in":"query","required":false,"description":"Extra representations. Repeat the parameter or comma-separate. text is always included.","schema":{"type":"array","items":{"type":"string","enum":["text","html","raw","headers"]}},"style":"form","explode":false},{"name":"preview_chars","in":"query","required":false,"description":"Characters of body text before truncation. Defaults to 2000, clamped at 100000. Parts larger than 524288 bytes are never inlined at all: the slice reports its size and its url.","schema":{"type":"integer","minimum":1,"maximum":100000}}],"responses":{"200":{"description":"The message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageView"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"message_not_found: unknown, erased or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"eraseMessage","summary":"Erase a message","description":"Tombstones the message immediately: it disappears from lists and from any wait already blocked on it. Stored bytes are removed by the reaper.","tags":["messages"],"security":[{"bearerAuth":[]}],"x-required-scope":"messages:delete","x-rate-limit-class":"write","parameters":[{"name":"id","in":"path","required":true,"description":"Message id (msg_…).","schema":{"type":"string"}}],"responses":{"200":{"description":"Erased. Not recoverable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageErased"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"message_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/usage":{"get":{"operationId":"getUsage","summary":"Current period usage","description":"Usage and limits for the current billing period, per metered metric.","tags":["usage"],"security":[{"bearerAuth":[]}],"x-required-scope":"usage:read","x-rate-limit-class":"read","responses":{"200":{"description":"Usage and limits for the current period.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/domains":{"post":{"operationId":"createDomain","summary":"Add a receiving domain","description":"Claims a domain for this project and mints its ownership token. The response carries the DNS records to publish, so onboarding is one call rather than two. The domain lands pending; nothing is routed to it until it verifies.","tags":["domains"],"security":[{"bearerAuth":[]}],"x-required-scope":"domains:write","x-rate-limit-class":"write","requestBody":{"description":"The domain to claim.","required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"domain":{"type":"string","minLength":1,"description":"A domain you control and can publish DNS under, e.g. \"mail.example.com\". Lowercased on write. Public suffixes and our own domains are refused."}},"required":["domain"],"additionalProperties":false}}}},"responses":{"201":{"description":"The domain, pending, with the DNS records to publish. Publish them, then call verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainCreated"}}}},"400":{"description":"invalid_request: not a domain we can receive on, or a public suffix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"conflict: already claimed. The message says whether it is yours or another project's; domain names are globally unique here.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listDomains","summary":"List receiving domains","description":"This project's domains, newest first. status is pending (never verified, still propagating), verified, failed (verified once and regressed — the one worth alerting on) or disabled.","tags":["domains"],"security":[{"bearerAuth":[]}],"x-required-scope":"domains:read","x-rate-limit-class":"read","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size. Defaults to 50, capped at 200.","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"next_cursor from the previous page. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainList"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/domains/{id}/verify":{"post":{"operationId":"verifyDomain","summary":"Re-check a domain's DNS","description":"Always a 200 carrying the current status and every check with the values actually observed. Unpropagated DNS is a state of the world, not a failed request, so poll this during onboarding and branch on status.","tags":["domains"],"security":[{"bearerAuth":[]}],"x-required-scope":"domains:write","x-rate-limit-class":"write","parameters":[{"name":"id","in":"path","required":true,"description":"Domain id (dom_…) or the domain name.","schema":{"type":"string"}}],"responses":{"200":{"description":"Always a 200, including when nothing is satisfied yet. Branch on status: \"pending\" means keep waiting for DNS, \"failed\" means it worked before and has regressed. Each check carries the values actually observed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainVerification"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"domain_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/domains/{id}":{"get":{"operationId":"getDomain","summary":"Get one receiving domain","description":"The domain, the DNS records it needs, and the result of the last verification if one has run. Accepts a domain id (dom_…) or the domain name.","tags":["domains"],"security":[{"bearerAuth":[]}],"x-required-scope":"domains:read","x-rate-limit-class":"read","parameters":[{"name":"id","in":"path","required":true,"description":"Domain id (dom_…) or the domain name.","schema":{"type":"string"}}],"responses":{"200":{"description":"The domain, the records it needs, and the last verification if one has run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainDetail"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"domain_not_found. A domain owned by another project reports the same thing: confirming it exists would be an enumeration oracle.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"disableDomain","summary":"Disable a receiving domain","description":"Stops mail immediately and permanently: a disabled domain is refused at the edge and verification will not re-enable it. Nothing is destroyed — messages stay readable until their retention TTL. To destroy data, call POST /v1/erasure.","tags":["domains"],"security":[{"bearerAuth":[]}],"x-required-scope":"domains:write","x-rate-limit-class":"write","parameters":[{"name":"id","in":"path","required":true,"description":"Domain id (dom_…) or the domain name.","schema":{"type":"string"}}],"responses":{"200":{"description":"Disabled and terminal: mail stops now, nothing is destroyed, and verification will not bring it back. The counts say what is still held. To destroy data, use POST /v1/erasure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainDisabled"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"domain_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks":{"post":{"operationId":"createWebhookEndpoint","summary":"Register a webhook endpoint","description":"Registers a URL to receive signed event POSTs and mints its signing secret. The URL is validated against the SSRF guard here, at registration, so an address we can never deliver to is refused while the caller is still looking at it rather than silently failing on the first event. The plaintext secret is in this response and in no other: store it now.","tags":["webhooks"],"security":[{"bearerAuth":[]}],"x-required-scope":"webhooks:write","x-rate-limit-class":"write","requestBody":{"description":"The endpoint to register.","required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"url":{"type":"string","minLength":1,"description":"An https URL on port 443 or 8443, with no credentials, whose hostname resolves only to public addresses. Checked here at registration rather than at first delivery."},"events":{"minItems":1,"type":"array","items":{"type":"string","enum":["message.received","message.expired","inbox.created","domain.verified","simulator.bounced","simulator.complained"]},"description":"Events to deliver. At least one; an empty list would be an endpoint that can never fire. Valid events: message.received, message.expired, inbox.created, domain.verified, simulator.bounced, simulator.complained."}},"required":["url","events"],"additionalProperties":false}}}},"responses":{"201":{"description":"The endpoint, including its signing secret in plaintext. This is the only response that ever carries the secret — it is never shown again by any read. Store it now; if you lose it, rotate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecret"}}}},"400":{"description":"invalid_request: an unknown event name, an empty events list, or a URL the SSRF guard refuses — not https, a port outside 443/8443, credentials in the URL, or a hostname resolving to a private, loopback or link-local address. The message names the offending value.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: DNS for the hostname could not be resolved at all. Retryable, and deliberately not invalid_request — a SERVFAIL now may answer on the next attempt.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listWebhookEndpoints","summary":"List webhook endpoints","description":"This project's endpoints, newest first. Secrets are never included; rotate one with POST /v1/webhooks/{id}/rotate-secret if you have lost it.","tags":["webhooks"],"security":[{"bearerAuth":[]}],"x-required-scope":"webhooks:read","x-rate-limit-class":"read","parameters":[{"name":"limit","in":"query","required":false,"description":"Page size. Defaults to 50, capped at 200.","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"next_cursor from the previous page. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Newest first. Never includes secrets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointList"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"Delivery log for one endpoint","description":"Every attempt against this endpoint, newest first, with the response status, the truncated response body, the error and the next scheduled retry. Filter by status to see only what is still pending or has exhausted its retries. This is how you find out why an event did not arrive without asking us.","tags":["webhooks"],"security":[{"bearerAuth":[]}],"x-required-scope":"webhooks:read","x-rate-limit-class":"read","parameters":[{"name":"id","in":"path","required":true,"description":"Webhook endpoint id (whe_…).","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Narrow to one status, e.g. exhausted for events that will never arrive.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","enum":["pending","delivered","failed","exhausted"],"description":"pending (queued or awaiting its next retry), delivered (2xx), failed (attempt failed, more retries to come) or exhausted (retries spent; this event will not arrive)."}},{"name":"limit","in":"query","required":false,"description":"Page size. Defaults to 50, capped at 200.","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"next_cursor from the previous page. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Newest first, with the response status, the truncated response body, the error and the next scheduled attempt. Dedupe on idempotency_key: delivery is at-least-once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryList"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"webhook_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}/rotate-secret":{"post":{"operationId":"rotateWebhookSecret","summary":"Rotate an endpoint's signing secret","description":"Mints a new secret and returns it once. The cutover is immediate and not overlapping: signatures on deliveries dispatched after this call verify against the new secret only, so deploy the new secret to your receiver before rotating, or accept a brief window of signature failures that will be retried.","tags":["webhooks"],"security":[{"bearerAuth":[]}],"x-required-scope":"webhooks:write","x-rate-limit-class":"write","parameters":[{"name":"id","in":"path","required":true,"description":"Webhook endpoint id (whe_…).","schema":{"type":"string"}}],"responses":{"200":{"description":"The endpoint with its new secret, returned once. The cutover is immediate and not overlapping: the old secret stops verifying at this call, so deploy the new one to your receiver first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecret"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"webhook_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{id}":{"get":{"operationId":"getWebhookEndpoint","summary":"Get one webhook endpoint","description":"The endpoint's URL, events and enabled state. Never the secret — that exists in plaintext only in the response that minted it.","tags":["webhooks"],"security":[{"bearerAuth":[]}],"x-required-scope":"webhooks:read","x-rate-limit-class":"read","parameters":[{"name":"id","in":"path","required":true,"description":"Webhook endpoint id (whe_…).","schema":{"type":"string"}}],"responses":{"200":{"description":"The endpoint. Never includes the secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"webhook_not_found. An endpoint owned by another project reports exactly this: confirming it exists would tell an attacker which ids are real.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateWebhookEndpoint","summary":"Change an endpoint's URL, events or enabled state","description":"Send only the fields you are changing; an omitted field is left alone. A changed url goes through the same SSRF guard as registration, so an endpoint cannot be edited into pointing at a private address. Set enabled to false to stop dispatch without losing the endpoint or its delivery history.","tags":["webhooks"],"security":[{"bearerAuth":[]}],"x-required-scope":"webhooks:write","x-rate-limit-class":"write","parameters":[{"name":"id","in":"path","required":true,"description":"Webhook endpoint id (whe_…).","schema":{"type":"string"}}],"requestBody":{"description":"The fields to change. At least one; an omitted field is left alone.","required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"url":{"type":"string","minLength":1,"description":"An https URL on port 443 or 8443, with no credentials, whose hostname resolves only to public addresses. Checked here at registration rather than at first delivery."},"events":{"minItems":1,"type":"array","items":{"type":"string","enum":["message.received","message.expired","inbox.created","domain.verified","simulator.bounced","simulator.complained"]},"description":"Events to deliver. At least one; an empty list would be an endpoint that can never fire. Valid events: message.received, message.expired, inbox.created, domain.verified, simulator.bounced, simulator.complained."},"enabled":{"type":"boolean"}},"additionalProperties":false}}}},"responses":{"200":{"description":"The endpoint as it now stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"invalid_request: no fields sent, an unknown event name, or a new url the SSRF guard refuses. A url identical to the current one is not re-checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"webhook_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: DNS for the new hostname failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteWebhookEndpoint","summary":"Delete a webhook endpoint","description":"Removes the endpoint and its delivery history, including anything still queued. To stop dispatch while keeping the log, PATCH enabled to false instead.","tags":["webhooks"],"security":[{"bearerAuth":[]}],"x-required-scope":"webhooks:write","x-rate-limit-class":"write","parameters":[{"name":"id","in":"path","required":true,"description":"Webhook endpoint id (whe_…).","schema":{"type":"string"}}],"responses":{"200":{"description":"Gone, with its delivery history and anything still queued. deliveries_discarded says how much went. To stop dispatch and keep the log, PATCH enabled to false instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointDeleted"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"webhook_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/erasure":{"post":{"operationId":"eraseData","summary":"Erase a message, an inbox's mail, or everything about an address","description":"Send exactly one of message_id, inbox_id or address. Tombstones synchronously: the data disappears from every read, including a wait already blocked on it. Stored bytes are hard-deleted by the reaper shortly after. Every erasure is written to the audit log.","tags":["data-protection"],"security":[{"bearerAuth":[]}],"x-required-scope":"messages:delete","x-rate-limit-class":"write","requestBody":{"description":"Exactly one selector.","required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"message_id":{"type":"string","description":"Erase one message."},"inbox_id":{"type":"string","description":"Erase every message in one inbox. The inbox address itself remains."},"address":{"type":"string","description":"Erase everything held about a person: mail addressed to them, mail they sent, and mail they were copied on. This is the shape a data-protection request arrives in."}},"additionalProperties":false,"description":"Exactly one selector. A body with none is refused rather than erasing everything, and a body with two is refused rather than guessing which was meant."}}}},"responses":{"200":{"description":"Erased. Already invisible to every read including a blocked wait; the bytes go shortly after. messages_erased may be zero, which means we held nothing about this subject — that is an answer, not a failure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Erasure"}}}},"400":{"description":"invalid_request: no selector, more than one, or an address that is not an address. None of the three is guessed at.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"message_not_found or inbox_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/audit":{"get":{"operationId":"listAuditEvents","summary":"Administrative audit trail","description":"Every administrative action against this project, newest first, plus the organisation-wide entries that affect it such as a suspension. action is a closed enum, so filter on it rather than parsing the message.","tags":["data-protection"],"security":[{"bearerAuth":[]}],"x-required-scope":"usage:read","x-rate-limit-class":"read","parameters":[{"name":"action","in":"query","required":false,"description":"Narrow to one action, e.g. every erasure.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","enum":["api_key.created","api_key.revoked","smtp_credential.created","smtp_credential.revoked","domain.added","domain.verified","domain.removed","inbox.purged","message.erased","inbox.erased","address.erased","messages.purged","retention.changed","webhook_endpoint.created","webhook_endpoint.updated","webhook_endpoint.deleted","tenant.suspended","tenant.reinstated","recipient.blocked","recipient.unblocked"],"description":"Closed set. Filter on this rather than parsing a message."}},{"name":"limit","in":"query","required":false,"description":"Page size. Defaults to 50, capped at 200.","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","required":false,"description":"next_cursor from the previous page. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Newest first, including organisation-wide entries such as a suspension.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditList"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/retention":{"get":{"operationId":"getRetention","summary":"Retention setting and what is stored","description":"The project's retention seconds, how many messages and bytes are held, the oldest arrival and when the next deletion happens.","tags":["data-protection"],"security":[{"bearerAuth":[]}],"x-required-scope":"usage:read","x-rate-limit-class":"read","responses":{"200":{"description":"The setting, and what is currently stored under it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Retention"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"setRetention","summary":"Change retention","description":"Applies to mail already stored, not only to future mail: shortening retention re-dates existing messages so they actually expire sooner. The project value is a ceiling — an inbox that asked for less keeps its own. Requires messages:delete because it can destroy stored mail.","tags":["data-protection"],"security":[{"bearerAuth":[]}],"x-required-scope":"messages:delete","x-rate-limit-class":"write","requestBody":{"description":"The new retention.","required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"retention_seconds":{"type":"integer","minimum":60,"maximum":2592000,"description":"Between 60 and 2592000 seconds. Applies to mail already stored: shortening it re-dates existing messages. The project value is a ceiling — an inbox that asked for less keeps its own."}},"required":["retention_seconds"],"additionalProperties":false}}}},"responses":{"200":{"description":"Applied, including to mail already stored: messages_redated counts the expiries that moved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetentionChanged"}}}},"400":{"description":"invalid_request: outside the permitted range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/region":{"get":{"operationId":"getRegion","summary":"Where this project's data lives","description":"The pinned region and a precise statement of what stays in it — message bodies, attachments and everything derived from them — versus what may not: account records, billing and operational telemetry.","tags":["data-protection"],"security":[{"bearerAuth":[]}],"x-required-scope":"usage:read","x-rate-limit-class":"read","responses":{"200":{"description":"What stays in region and what may not, category by category.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Region"}}}},"400":{"description":"invalid_request: the request could not be interpreted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"unauthenticated or invalid_api_key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"quota_exceeded: the monthly allowance is spent. Inbound mail is still being accepted and stored; only this read was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"insufficient_scope, forbidden or abuse_suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited or concurrency_limit. Read retry_after_seconds. Blocking waits are never rate limited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"internal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"storage_unavailable: the object store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key: `Authorization: Bearer is_live_…` or `is_test_…`. Each key carries scopes; a route that needs one you lack fails with insufficient_scope, never silently."}},"schemas":{"AuthCheck":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","const":"authenticated"},"key":{"type":"object","properties":{"id":{"type":"string"},"environment":{"anyOf":[{"type":"string","enum":["live","test"]},{"type":"null"}]},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","environment","expires_at"],"additionalProperties":false},"project":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"}},"required":["id","key"],"additionalProperties":false},"scopes":{"type":"array","items":{"type":"string","enum":["inboxes:read","inboxes:write","messages:read","messages:wait","messages:delete","domains:read","domains:write","webhooks:read","webhooks:write","usage:read"]}},"server":{"type":"object","properties":{"environment":{"type":"string","enum":["local","staging","production","unknown"]},"region":{"type":"string","enum":["us","eu"]},"api_origin":{"type":"string"}},"required":["environment","region","api_origin"],"additionalProperties":false}},"required":["status","key","project","scopes","server"],"additionalProperties":false},"ErrorCode":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","enum":["unauthenticated","invalid_api_key","insufficient_scope","forbidden","inbox_not_found","message_not_found","attachment_not_found","domain_not_found","domain_not_verified","project_not_found","webhook_not_found","article_not_found","invalid_request","unsupported_format","payload_too_large","conflict","quota_exceeded","organisation_limit_exceeded","rate_limited","concurrency_limit","blocked_recipient","abuse_suspended","ingest_ceiling_exceeded","storage_unavailable","internal"],"description":"Stable machine-readable failure code. Branch on this, never on the HTTP status or the message text."},"Error":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unauthenticated","invalid_api_key","insufficient_scope","forbidden","inbox_not_found","message_not_found","attachment_not_found","domain_not_found","domain_not_verified","project_not_found","webhook_not_found","article_not_found","invalid_request","unsupported_format","payload_too_large","conflict","quota_exceeded","organisation_limit_exceeded","rate_limited","concurrency_limit","blocked_recipient","abuse_suspended","ingest_ceiling_exceeded","storage_unavailable","internal"],"description":"Stable machine-readable failure code. Branch on this, never on the HTTP status or the message text."},"message":{"type":"string","description":"Human-readable, not machine-parseable."},"retryable":{"type":"boolean","description":"True when an identical retry could succeed later with no caller change."},"retry_after_seconds":{"type":"number"},"remedy":{"type":"string","description":"The concrete next step, written for a machine."},"docs":{"type":"string"},"details":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["code","message","retryable","docs"],"additionalProperties":false}},"required":["error"],"additionalProperties":false,"description":"The envelope on every failure, from every surface. An empty result set is never an error: it means the inbox exists, quota is available, and nothing matched."},"EmailAddress":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"type":"string"}},"required":["name","address"],"additionalProperties":false},"ExtractedCode":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"value":{"type":"string","description":"The code exactly as it appeared in the message."},"normalised":{"type":"string","description":"Separators removed, e.g. \"482-913\" becomes \"482913\"."},"confidence":{"type":"number","description":"0..1. Filter on this to keep only obvious codes."},"context":{"type":"string","description":"The phrase that anchored the match, for debugging a false positive."}},"required":["value","normalised","confidence","context"],"additionalProperties":false},"ExtractedLink":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"text":{"anyOf":[{"type":"string"},{"type":"null"}]},"href":{"type":"string"},"kind":{"type":"string","enum":["verify","sign_in","unsubscribe","reset_password","other"]},"original_href":{"type":"string","description":"Present when href was unwrapped from a click tracker. InboxSpec never fetches a link to resolve it."},"source":{"type":"string","enum":["html","text"]}},"required":["text","href","kind","source"],"additionalProperties":false},"BodySlice":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"preview":{"type":"string","description":"Truncated body text. Empty on list responses, which never inline bodies; fetch url instead."},"truncated":{"type":"boolean"},"total_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"url":{"type":"string","description":"Where the whole part lives. Always present."}},"required":["preview","truncated","total_bytes","url"],"additionalProperties":false},"AttachmentSummary":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}]},"content_type":{"type":"string"},"size_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sha256":{"type":"string"},"content_id":{"anyOf":[{"type":"string"},{"type":"null"}]},"is_inline":{"type":"boolean"},"malware":{"anyOf":[{"type":"object","properties":{"scanned":{"type":"boolean"},"infected":{"type":"boolean"},"signature":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["scanned","infected","signature"],"additionalProperties":false,"description":"null means NO SCAN RAN — either no scanner is configured or the scan failed. That is not a clean bill of health, and it is deliberately not reported as infected:false. The only clean result is scanned:true with infected:false."},{"type":"null"}]},"download_url":{"type":"string","description":"Time-limited signed URL. Fetch it with NO Authorization header — the signature is its credential, and sending a Bearer token is harmless but unnecessary. Depending on the deployment's storage backend it points at the object store or at a signed download route on this origin."}},"required":["id","filename","content_type","size_bytes","sha256","content_id","is_inline","malware","download_url"],"additionalProperties":false},"MessageView":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"inbox":{"type":"string"},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}]},"from":{"anyOf":[{"type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"type":"string"}},"required":["name","address"],"additionalProperties":false},{"type":"null"}]},"to":{"type":"array","items":{"type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"type":"string"}},"required":["name","address"],"additionalProperties":false}},"cc":{"type":"array","items":{"type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"type":"string"}},"required":["name","address"],"additionalProperties":false}},"reply_to":{"type":"array","items":{"type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"type":"string"}},"required":["name","address"],"additionalProperties":false}},"subject":{"anyOf":[{"type":"string"},{"type":"null"}]},"received_at":{"type":"string","description":"ISO 8601. Server-authoritative, never the Date header."},"expires_at":{"description":"Stored message expiry. Automatic cleanup removes message content after this time; the inbox remains.","anyOf":[{"type":"string"},{"type":"null"}]},"size_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"codes":{"type":"array","items":{"type":"string"},"description":"One-time codes in normalised form, best candidate first. This is what you type into a form."},"codes_detail":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","description":"The code exactly as it appeared in the message."},"normalised":{"type":"string","description":"Separators removed, e.g. \"482-913\" becomes \"482913\"."},"confidence":{"type":"number","description":"0..1. Filter on this to keep only obvious codes."},"context":{"type":"string","description":"The phrase that anchored the match, for debugging a false positive."}},"required":["value","normalised","confidence","context"],"additionalProperties":false}},"links":{"type":"array","items":{"type":"object","properties":{"text":{"anyOf":[{"type":"string"},{"type":"null"}]},"href":{"type":"string"},"kind":{"type":"string","enum":["verify","sign_in","unsubscribe","reset_password","other"]},"original_href":{"type":"string","description":"Present when href was unwrapped from a click tracker. InboxSpec never fetches a link to resolve it."},"source":{"type":"string","enum":["html","text"]}},"required":["text","href","kind","source"],"additionalProperties":false}},"text":{"type":"object","properties":{"preview":{"type":"string","description":"Truncated body text. Empty on list responses, which never inline bodies; fetch url instead."},"truncated":{"type":"boolean"},"total_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"url":{"type":"string","description":"Where the whole part lives. Always present."}},"required":["preview","truncated","total_bytes","url"],"additionalProperties":false},"html":{"type":"object","properties":{"preview":{"type":"string","description":"Truncated body text. Empty on list responses, which never inline bodies; fetch url instead."},"truncated":{"type":"boolean"},"total_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"url":{"type":"string","description":"Where the whole part lives. Always present."}},"required":["preview","truncated","total_bytes","url"],"additionalProperties":false,"description":"Only when format includes html."},"raw":{"type":"object","properties":{"preview":{"type":"string","description":"Truncated body text. Empty on list responses, which never inline bodies; fetch url instead."},"truncated":{"type":"boolean"},"total_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"url":{"type":"string","description":"Where the whole part lives. Always present."}},"required":["preview","truncated","total_bytes","url"],"additionalProperties":false,"description":"Only when format includes raw."},"headers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}},"description":"Lowercased header names to values. Only when format includes headers."},"attachments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}]},"content_type":{"type":"string"},"size_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sha256":{"type":"string"},"content_id":{"anyOf":[{"type":"string"},{"type":"null"}]},"is_inline":{"type":"boolean"},"malware":{"anyOf":[{"type":"object","properties":{"scanned":{"type":"boolean"},"infected":{"type":"boolean"},"signature":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["scanned","infected","signature"],"additionalProperties":false,"description":"null means NO SCAN RAN — either no scanner is configured or the scan failed. That is not a clean bill of health, and it is deliberately not reported as infected:false. The only clean result is scanned:true with infected:false."},{"type":"null"}]},"download_url":{"type":"string","description":"Time-limited signed URL. Fetch it with NO Authorization header — the signature is its credential, and sending a Bearer token is harmless but unnecessary. Depending on the deployment's storage backend it points at the object store or at a signed download route on this origin."}},"required":["id","filename","content_type","size_bytes","sha256","content_id","is_inline","malware","download_url"],"additionalProperties":false}},"authentication":{"type":"object","properties":{"spf":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","softfail","neutral","none","temperror","permerror"]},"domain":{"anyOf":[{"type":"string"},{"type":"null"}]},"client_ip":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["verdict","domain","client_ip"],"additionalProperties":false},"dkim":{"type":"array","items":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","softfail","neutral","none","temperror","permerror"]},"domain":{"anyOf":[{"type":"string"},{"type":"null"}]},"selector":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["verdict","domain","selector"],"additionalProperties":false}},"dmarc":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","softfail","neutral","none","temperror","permerror"]},"domain":{"anyOf":[{"type":"string"},{"type":"null"}]},"policy":{"anyOf":[{"type":"string"},{"type":"null"}]},"aligned":{"type":"boolean"}},"required":["verdict","domain","policy","aligned"],"additionalProperties":false}},"required":["spf","dkim","dmarc"],"additionalProperties":false},"spam":{"anyOf":[{"type":"object","properties":{"score":{"type":"number"},"threshold":{"type":"number"},"is_spam":{"type":"boolean"},"symbols":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"score":{"type":"number"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["name","score","description"],"additionalProperties":false}}},"required":["score","threshold","is_spam","symbols"],"additionalProperties":false},{"type":"null"}]},"simulated":{"anyOf":[{"type":"string","enum":["hard_bounce","soft_bounce","complaint","delay","oversize","greylist","success"],"description":"Set when a simulator address produced this message."},{"type":"null"}]},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal parse or storage anomalies, e.g. a guessed charset or an extraction list that hit its cap. Empty on a clean message. Read this first when a code or link you expected is missing: it says whether the body was salvaged rather than read cleanly."}},"required":["id","inbox","message_id","from","to","cc","reply_to","subject","received_at","size_bytes","codes","codes_detail","links","text","attachments","authentication","spam","simulated","warnings"],"additionalProperties":false,"description":"The default message representation. Bodies are truncated with a pointer to the whole thing; html, raw and headers are opt-in through format."},"MessageMatch":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"subject_contains":{"type":"string","description":"Substring of the whole subject line, punctuation included."},"from_contains":{"type":"string","description":"Substring of the From address and display name, searched together."},"to_contains":{"type":"string","description":"Substring of this message's recipients. A delivery that arrived by BCC carries no To entry for the inbox."},"body_contains":{"type":"string","description":"Substring of the subject and the plain-text part. Never the HTML, so an HTML-only message matches on its subject alone."},"correlation_id":{"description":"Exact case-sensitive X-InboxSpec-Correlation-ID header value.","type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"},"header_contains":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Header name to a substring its value must contain."},"since":{"type":"string","description":"ISO 8601 lower bound on received_at."},"has_code":{"type":"boolean"},"has_link_kind":{"type":"string","enum":["verify","sign_in","unsubscribe","reset_password","other"]},"message_id":{"type":"string","description":"Exact stored RFC 5322 Message-ID. Matching is non-consuming, without durable response replay."}},"additionalProperties":false,"description":"All fields are ANDed; string fields are case-insensitive substrings. An empty substring is rejected because it would match everything."},"MessageList":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"inbox":{"type":"string"},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}]},"from":{"anyOf":[{"type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"type":"string"}},"required":["name","address"],"additionalProperties":false},{"type":"null"}]},"to":{"type":"array","items":{"type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"type":"string"}},"required":["name","address"],"additionalProperties":false}},"cc":{"type":"array","items":{"type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"type":"string"}},"required":["name","address"],"additionalProperties":false}},"reply_to":{"type":"array","items":{"type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"type":"string"}},"required":["name","address"],"additionalProperties":false}},"subject":{"anyOf":[{"type":"string"},{"type":"null"}]},"received_at":{"type":"string","description":"ISO 8601. Server-authoritative, never the Date header."},"expires_at":{"description":"Stored message expiry. Automatic cleanup removes message content after this time; the inbox remains.","anyOf":[{"type":"string"},{"type":"null"}]},"size_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"codes":{"type":"array","items":{"type":"string"},"description":"One-time codes in normalised form, best candidate first. This is what you type into a form."},"codes_detail":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","description":"The code exactly as it appeared in the message."},"normalised":{"type":"string","description":"Separators removed, e.g. \"482-913\" becomes \"482913\"."},"confidence":{"type":"number","description":"0..1. Filter on this to keep only obvious codes."},"context":{"type":"string","description":"The phrase that anchored the match, for debugging a false positive."}},"required":["value","normalised","confidence","context"],"additionalProperties":false}},"links":{"type":"array","items":{"type":"object","properties":{"text":{"anyOf":[{"type":"string"},{"type":"null"}]},"href":{"type":"string"},"kind":{"type":"string","enum":["verify","sign_in","unsubscribe","reset_password","other"]},"original_href":{"type":"string","description":"Present when href was unwrapped from a click tracker. InboxSpec never fetches a link to resolve it."},"source":{"type":"string","enum":["html","text"]}},"required":["text","href","kind","source"],"additionalProperties":false}},"text":{"type":"object","properties":{"preview":{"type":"string","description":"Truncated body text. Empty on list responses, which never inline bodies; fetch url instead."},"truncated":{"type":"boolean"},"total_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"url":{"type":"string","description":"Where the whole part lives. Always present."}},"required":["preview","truncated","total_bytes","url"],"additionalProperties":false},"html":{"type":"object","properties":{"preview":{"type":"string","description":"Truncated body text. Empty on list responses, which never inline bodies; fetch url instead."},"truncated":{"type":"boolean"},"total_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"url":{"type":"string","description":"Where the whole part lives. Always present."}},"required":["preview","truncated","total_bytes","url"],"additionalProperties":false,"description":"Only when format includes html."},"raw":{"type":"object","properties":{"preview":{"type":"string","description":"Truncated body text. Empty on list responses, which never inline bodies; fetch url instead."},"truncated":{"type":"boolean"},"total_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"url":{"type":"string","description":"Where the whole part lives. Always present."}},"required":["preview","truncated","total_bytes","url"],"additionalProperties":false,"description":"Only when format includes raw."},"headers":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"string"}},"description":"Lowercased header names to values. Only when format includes headers."},"attachments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"filename":{"anyOf":[{"type":"string"},{"type":"null"}]},"content_type":{"type":"string"},"size_bytes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sha256":{"type":"string"},"content_id":{"anyOf":[{"type":"string"},{"type":"null"}]},"is_inline":{"type":"boolean"},"malware":{"anyOf":[{"type":"object","properties":{"scanned":{"type":"boolean"},"infected":{"type":"boolean"},"signature":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["scanned","infected","signature"],"additionalProperties":false,"description":"null means NO SCAN RAN — either no scanner is configured or the scan failed. That is not a clean bill of health, and it is deliberately not reported as infected:false. The only clean result is scanned:true with infected:false."},{"type":"null"}]},"download_url":{"type":"string","description":"Time-limited signed URL. Fetch it with NO Authorization header — the signature is its credential, and sending a Bearer token is harmless but unnecessary. Depending on the deployment's storage backend it points at the object store or at a signed download route on this origin."}},"required":["id","filename","content_type","size_bytes","sha256","content_id","is_inline","malware","download_url"],"additionalProperties":false}},"authentication":{"type":"object","properties":{"spf":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","softfail","neutral","none","temperror","permerror"]},"domain":{"anyOf":[{"type":"string"},{"type":"null"}]},"client_ip":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["verdict","domain","client_ip"],"additionalProperties":false},"dkim":{"type":"array","items":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","softfail","neutral","none","temperror","permerror"]},"domain":{"anyOf":[{"type":"string"},{"type":"null"}]},"selector":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["verdict","domain","selector"],"additionalProperties":false}},"dmarc":{"type":"object","properties":{"verdict":{"type":"string","enum":["pass","fail","softfail","neutral","none","temperror","permerror"]},"domain":{"anyOf":[{"type":"string"},{"type":"null"}]},"policy":{"anyOf":[{"type":"string"},{"type":"null"}]},"aligned":{"type":"boolean"}},"required":["verdict","domain","policy","aligned"],"additionalProperties":false}},"required":["spf","dkim","dmarc"],"additionalProperties":false},"spam":{"anyOf":[{"type":"object","properties":{"score":{"type":"number"},"threshold":{"type":"number"},"is_spam":{"type":"boolean"},"symbols":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"score":{"type":"number"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["name","score","description"],"additionalProperties":false}}},"required":["score","threshold","is_spam","symbols"],"additionalProperties":false},{"type":"null"}]},"simulated":{"anyOf":[{"type":"string","enum":["hard_bounce","soft_bounce","complaint","delay","oversize","greylist","success"],"description":"Set when a simulator address produced this message."},{"type":"null"}]},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal parse or storage anomalies, e.g. a guessed charset or an extraction list that hit its cap. Empty on a clean message. Read this first when a code or link you expected is missing: it says whether the body was salvaged rather than read cleanly."}},"required":["id","inbox","message_id","from","to","cc","reply_to","subject","received_at","size_bytes","codes","codes_detail","links","text","attachments","authentication","spam","simulated","warnings"],"additionalProperties":false,"description":"The default message representation. Bodies are truncated with a pointer to the whole thing; html, raw and headers are opt-in through format."}},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["data","next_cursor"],"additionalProperties":false},"MessageErased":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"erased":{"type":"boolean","const":true},"id":{"type":"string"},"audit_event_id":{"type":"string","description":"The audit row this erasure wrote. Quote it in your own records."}},"required":["erased","id","audit_event_id"],"additionalProperties":false},"WaitRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"inbox":{"type":"string","description":"Inbox id or address. Must already exist."},"match":{"type":"object","properties":{"subject_contains":{"type":"string","description":"Substring of the whole subject line, punctuation included."},"from_contains":{"type":"string","description":"Substring of the From address and display name, searched together."},"to_contains":{"type":"string","description":"Substring of this message's recipients. A delivery that arrived by BCC carries no To entry for the inbox."},"body_contains":{"type":"string","description":"Substring of the subject and the plain-text part. Never the HTML, so an HTML-only message matches on its subject alone."},"correlation_id":{"description":"Exact case-sensitive X-InboxSpec-Correlation-ID header value.","type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$"},"header_contains":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"},"description":"Header name to a substring its value must contain."},"since":{"type":"string","description":"ISO 8601 lower bound on received_at."},"has_code":{"type":"boolean"},"has_link_kind":{"type":"string","enum":["verify","sign_in","unsubscribe","reset_password","other"]},"message_id":{"type":"string","description":"Exact stored RFC 5322 Message-ID. Matching is non-consuming, without durable response replay."}},"additionalProperties":false,"description":"Omit to take the next message that arrives."},"timeout_ms":{"type":"integer","minimum":1,"maximum":285000,"description":"Defaults to 120000. Above 285000 is rejected, never clamped."},"format":{"type":"array","items":{"type":"string","enum":["text","html","raw","headers"]}},"preview_chars":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Characters of body text before truncation. Defaults to 2000, clamped at 100000 rather than refused."}},"required":["inbox"],"additionalProperties":false,"description":"Blocks until a message matches. Never rate limited and never metered. A timeout is a result, not an error: it carries a count of non-matching arrivals and a specific hint."},"WaitTimeout":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","const":"timeout"},"request_id":{"type":"string"},"started_at":{"type":"string"},"completed_at":{"type":"string"},"reason":{"type":"string","enum":["no_arrivals","no_matching_arrivals"]},"waited_ms":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"inbox":{"type":"string"},"non_matching_arrivals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Messages that arrived during the wait but failed the match."},"hint":{"type":"string","description":"The most likely reason and what to change, written for a model."}},"required":["status","waited_ms","inbox","non_matching_arrivals","hint"],"additionalProperties":false},"Inbox":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"address":{"type":"string"},"tag":{"type":"string","description":"Local part below the project namespace, dot-nested."},"labels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"retention_seconds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Message retention in seconds, not inbox lifetime."},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Legacy field; null because inboxes remain until explicitly deleted."},"created_at":{"type":"string"},"last_message_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"message_count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","address","tag","labels","retention_seconds","expires_at","created_at","last_message_at","message_count"],"additionalProperties":false},"InboxList":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"address":{"type":"string"},"tag":{"type":"string","description":"Local part below the project namespace, dot-nested."},"labels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"retention_seconds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Message retention in seconds, not inbox lifetime."},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Legacy field; null because inboxes remain until explicitly deleted."},"created_at":{"type":"string"},"last_message_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"message_count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","address","tag","labels","retention_seconds","expires_at","created_at","last_message_at","message_count"],"additionalProperties":false}},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["data","next_cursor"],"additionalProperties":false},"InboxDeleted":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"deleted":{"type":"boolean"},"messages_purged":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"objects_deleted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["deleted","messages_purged","objects_deleted"],"additionalProperties":false},"StreamToken":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"token":{"type":"string","description":"Shown once. Only a SHA-256 is stored, so it cannot be re-read."},"expires_at":{"type":"string"},"inbox":{"type":"string"},"url":{"type":"string","description":"The stream URL with the token already in it. Hand this straight to `new EventSource(url)`."}},"required":["token","expires_at","inbox","url"],"additionalProperties":false},"CleanupResult":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"inboxes_deleted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"messages_purged":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"objects_deleted":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["inboxes_deleted","messages_purged","objects_deleted"],"additionalProperties":false},"Usage":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"organisation":{"description":"Organisation-pooled limits when enabled; metrics is then empty. Null usage means accounting is not ready, not zero consumption.","type":"object","properties":{"organisation_id":{"type":"string"},"plan_id":{"type":"string","enum":["free","starter","developer","team"]},"billing_status":{"type":"string","enum":["disabled","active","grace","paused"]},"accounting_ready":{"type":"boolean"},"period_start":{"type":"string"},"period_end":{"type":"string"},"included_messages":{"type":"integer","minimum":0,"maximum":9007199254740991},"accepted_messages":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"retained_bytes":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"retained_limit_bytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"downloaded_bytes":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"reserved_download_bytes":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"download_limit_bytes":{"type":"integer","minimum":0,"maximum":9007199254740991},"overage_enabled":{"type":"boolean"},"overage_budget_usd_cents":{"type":"integer","minimum":0,"maximum":9007199254740991},"overage_spend_usd_cents":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"base_price_usd_cents":{"type":"integer","minimum":0,"maximum":9007199254740991},"maximum_before_tax_usd_cents":{"type":"integer","minimum":0,"maximum":9007199254740991},"message_warning":{"type":"string","enum":["unknown","none","approaching","exhausted"]},"storage_warning":{"type":"string","enum":["unknown","none","approaching","exhausted"]},"download_warning":{"type":"string","enum":["unknown","none","approaching","exhausted"]},"budget_warning":{"type":"string","enum":["unknown","none","approaching","exhausted"]}},"required":["organisation_id","plan_id","billing_status","accounting_ready","period_start","period_end","included_messages","accepted_messages","retained_bytes","retained_limit_bytes","downloaded_bytes","reserved_download_bytes","download_limit_bytes","overage_enabled","overage_budget_usd_cents","overage_spend_usd_cents","base_price_usd_cents","maximum_before_tax_usd_cents","message_warning","storage_warning","download_warning","budget_warning"],"additionalProperties":false},"period":{"type":"string","description":"YYYY-MM of the current billing period."},"metrics":{"type":"array","items":{"type":"object","properties":{"metric":{"type":"string","enum":["message.received","message.captured","attachment.bytes_stored","domain.active_day"]},"used":{"type":"number","description":"Consumed so far this period, from our ledger."},"limit":{"type":"number","description":"The project's plan allowance. Zero or negative means unlimited."},"allowed":{"type":"boolean","description":"False once reads are refused. Inbound mail is still stored."},"period_resets_at":{"type":"string","description":"ISO 8601 instant used returns to zero."}},"required":["metric","used","limit","allowed","period_resets_at"],"additionalProperties":false}}},"required":["period","metrics"],"additionalProperties":false},"DnsRequirement":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"kind":{"type":"string","enum":["MX","TXT","CNAME"]},"name":{"type":"string","description":"Fully-qualified name to publish the record at."},"value":{"type":"string"},"priority":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"MX preference. Present only on MX records."},"purpose":{"type":"string","description":"Why this record exists, in one sentence."},"required":{"type":"boolean","description":"False means receiving works without it; publish it only if it applies."}},"required":["kind","name","value","purpose","required"],"additionalProperties":false},"DnsCheck":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"kind":{"type":"string","enum":["MX","TXT","CNAME"]},"name":{"type":"string","description":"Fully-qualified name to publish the record at."},"value":{"type":"string"},"priority":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"MX preference. Present only on MX records."},"purpose":{"type":"string","description":"Why this record exists, in one sentence."},"required":{"type":"boolean","description":"False means receiving works without it; publish it only if it applies."},"satisfied":{"type":"boolean"},"observed":{"type":"array","items":{"type":"string"},"description":"What DNS actually returned for this name. The only thing that makes a failed verification actionable — compare it against value."}},"required":["kind","name","value","purpose","required","satisfied","observed"],"additionalProperties":false},"Domain":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"kind":{"type":"string","enum":["shared","dedicated","custom"]},"status":{"type":"string","enum":["pending","verified","failed","disabled"],"description":"pending: never verified, DNS may still be propagating. verified: receiving. failed: it verified once and regressed — this is the one worth alerting on. disabled: switched off by an operator or by DELETE, and terminal; verification will not re-enable it."},"verification_token":{"type":"string","description":"The token embedded in the ownership TXT record."},"catch_all":{"type":"boolean","description":"Unknown local parts are provisioned as inboxes on first receipt."},"verified_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"last_checked_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"type":"string"}},"required":["id","domain","kind","status","verification_token","catch_all","verified_at","last_checked_at","created_at"],"additionalProperties":false},"DomainCreated":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"kind":{"type":"string","enum":["shared","dedicated","custom"]},"status":{"type":"string","enum":["pending","verified","failed","disabled"],"description":"pending: never verified, DNS may still be propagating. verified: receiving. failed: it verified once and regressed — this is the one worth alerting on. disabled: switched off by an operator or by DELETE, and terminal; verification will not re-enable it."},"verification_token":{"type":"string","description":"The token embedded in the ownership TXT record."},"catch_all":{"type":"boolean","description":"Unknown local parts are provisioned as inboxes on first receipt."},"verified_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"last_checked_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"type":"string"},"dns_records":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["MX","TXT","CNAME"]},"name":{"type":"string","description":"Fully-qualified name to publish the record at."},"value":{"type":"string"},"priority":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"MX preference. Present only on MX records."},"purpose":{"type":"string","description":"Why this record exists, in one sentence."},"required":{"type":"boolean","description":"False means receiving works without it; publish it only if it applies."}},"required":["kind","name","value","purpose","required"],"additionalProperties":false},"description":"Publish these, then call verify. Returned here so adding a domain and getting its records is one call."}},"required":["id","domain","kind","status","verification_token","catch_all","verified_at","last_checked_at","created_at","dns_records"],"additionalProperties":false},"DomainDetail":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"kind":{"type":"string","enum":["shared","dedicated","custom"]},"status":{"type":"string","enum":["pending","verified","failed","disabled"],"description":"pending: never verified, DNS may still be propagating. verified: receiving. failed: it verified once and regressed — this is the one worth alerting on. disabled: switched off by an operator or by DELETE, and terminal; verification will not re-enable it."},"verification_token":{"type":"string","description":"The token embedded in the ownership TXT record."},"catch_all":{"type":"boolean","description":"Unknown local parts are provisioned as inboxes on first receipt."},"verified_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"last_checked_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"type":"string"},"dns_records":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["MX","TXT","CNAME"]},"name":{"type":"string","description":"Fully-qualified name to publish the record at."},"value":{"type":"string"},"priority":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"MX preference. Present only on MX records."},"purpose":{"type":"string","description":"Why this record exists, in one sentence."},"required":{"type":"boolean","description":"False means receiving works without it; publish it only if it applies."}},"required":["kind","name","value","purpose","required"],"additionalProperties":false},"description":"Publish these, then call verify. Returned here so adding a domain and getting its records is one call."},"last_check":{"anyOf":[{"type":"object","properties":{"checked_at":{"type":"string"},"status":{"type":"string","enum":["pending","verified","failed","disabled"],"description":"pending: never verified, DNS may still be propagating. verified: receiving. failed: it verified once and regressed — this is the one worth alerting on. disabled: switched off by an operator or by DELETE, and terminal; verification will not re-enable it."},"checks":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["MX","TXT","CNAME"]},"name":{"type":"string","description":"Fully-qualified name to publish the record at."},"value":{"type":"string"},"priority":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"MX preference. Present only on MX records."},"purpose":{"type":"string","description":"Why this record exists, in one sentence."},"required":{"type":"boolean","description":"False means receiving works without it; publish it only if it applies."},"satisfied":{"type":"boolean"},"observed":{"type":"array","items":{"type":"string"},"description":"What DNS actually returned for this name. The only thing that makes a failed verification actionable — compare it against value."}},"required":["kind","name","value","purpose","required","satisfied","observed"],"additionalProperties":false}}},"required":["checked_at","status","checks"],"additionalProperties":false,"description":"The most recent verification, as it was recorded. Null if never run."},{"type":"null"}]}},"required":["id","domain","kind","status","verification_token","catch_all","verified_at","last_checked_at","created_at","dns_records","last_check"],"additionalProperties":false},"DomainList":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"kind":{"type":"string","enum":["shared","dedicated","custom"]},"status":{"type":"string","enum":["pending","verified","failed","disabled"],"description":"pending: never verified, DNS may still be propagating. verified: receiving. failed: it verified once and regressed — this is the one worth alerting on. disabled: switched off by an operator or by DELETE, and terminal; verification will not re-enable it."},"verification_token":{"type":"string","description":"The token embedded in the ownership TXT record."},"catch_all":{"type":"boolean","description":"Unknown local parts are provisioned as inboxes on first receipt."},"verified_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"last_checked_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"type":"string"}},"required":["id","domain","kind","status","verification_token","catch_all","verified_at","last_checked_at","created_at"],"additionalProperties":false}},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["data","next_cursor"],"additionalProperties":false},"DomainVerification":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","enum":["pending","verified","failed","disabled"],"description":"pending: never verified, DNS may still be propagating. verified: receiving. failed: it verified once and regressed — this is the one worth alerting on. disabled: switched off by an operator or by DELETE, and terminal; verification will not re-enable it."},"checks":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["MX","TXT","CNAME"]},"name":{"type":"string","description":"Fully-qualified name to publish the record at."},"value":{"type":"string"},"priority":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"MX preference. Present only on MX records."},"purpose":{"type":"string","description":"Why this record exists, in one sentence."},"required":{"type":"boolean","description":"False means receiving works without it; publish it only if it applies."},"satisfied":{"type":"boolean"},"observed":{"type":"array","items":{"type":"string"},"description":"What DNS actually returned for this name. The only thing that makes a failed verification actionable — compare it against value."}},"required":["kind","name","value","purpose","required","satisfied","observed"],"additionalProperties":false}}},"required":["status","checks"],"additionalProperties":false},"DomainDisabled":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"kind":{"type":"string","enum":["shared","dedicated","custom"]},"status":{"type":"string","enum":["pending","verified","failed","disabled"],"description":"pending: never verified, DNS may still be propagating. verified: receiving. failed: it verified once and regressed — this is the one worth alerting on. disabled: switched off by an operator or by DELETE, and terminal; verification will not re-enable it."},"verification_token":{"type":"string","description":"The token embedded in the ownership TXT record."},"catch_all":{"type":"boolean","description":"Unknown local parts are provisioned as inboxes on first receipt."},"verified_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"last_checked_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"type":"string"},"inboxes_retained":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Inboxes still on this domain. Nothing was deleted."},"messages_retained":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Messages still readable under it, until their retention TTL."},"remedy":{"type":"string","description":"What to call instead if the intent was to destroy the data rather than stop mail."}},"required":["id","domain","kind","status","verification_token","catch_all","verified_at","last_checked_at","created_at","inboxes_retained","messages_retained","remedy"],"additionalProperties":false},"WebhookEndpoint":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string","enum":["message.received","message.expired","inbox.created","domain.verified","simulator.bounced","simulator.complained"]}},"enabled":{"type":"boolean","description":"False stops dispatch. The endpoint and its delivery log are kept."},"created_at":{"type":"string"}},"required":["id","url","events","enabled","created_at"],"additionalProperties":false},"WebhookEndpointWithSecret":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string","enum":["message.received","message.expired","inbox.created","domain.verified","simulator.bounced","simulator.complained"]}},"enabled":{"type":"boolean","description":"False stops dispatch. The endpoint and its delivery log are kept."},"created_at":{"type":"string"},"secret":{"type":"string","description":"The HMAC signing secret, in plaintext, in this response only. Never returned by any read, and not recoverable — if you lose it, rotate."}},"required":["id","url","events","enabled","created_at","secret"],"additionalProperties":false},"WebhookEndpointList":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string","enum":["message.received","message.expired","inbox.created","domain.verified","simulator.bounced","simulator.complained"]}},"enabled":{"type":"boolean","description":"False stops dispatch. The endpoint and its delivery log are kept."},"created_at":{"type":"string"}},"required":["id","url","events","enabled","created_at"],"additionalProperties":false}},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["data","next_cursor"],"additionalProperties":false},"WebhookEndpointDeleted":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"deleted":{"type":"boolean","const":true},"id":{"type":"string"},"deliveries_discarded":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Delivery rows removed with it, including anything still queued."}},"required":["deleted","id","deliveries_discarded"],"additionalProperties":false},"WebhookDeliveryStatus":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","enum":["pending","delivered","failed","exhausted"],"description":"pending (queued or awaiting its next retry), delivered (2xx), failed (attempt failed, more retries to come) or exhausted (retries spent; this event will not arrive)."},"WebhookDelivery":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"endpoint_id":{"type":"string"},"event":{"type":"string","enum":["message.received","message.expired","inbox.created","domain.verified","simulator.bounced","simulator.complained"]},"idempotency_key":{"type":"string","description":"Stable across every retry of one event. Dedupe on this: delivery is at-least-once by design."},"attempt":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Attempts made so far. Zero means it has not been tried yet."},"status":{"type":"string","enum":["pending","delivered","failed","exhausted"],"description":"pending (queued or awaiting its next retry), delivered (2xx), failed (attempt failed, more retries to come) or exhausted (retries spent; this event will not arrive)."},"response_status":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"HTTP status your endpoint returned, or null if it never answered."},"response_body":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Truncated. Enough to debug, not enough to store your page."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Why the attempt failed when there was no response at all."},"scheduled_for":{"type":"string","description":"When the next attempt runs, with exponential backoff. In the past for a delivery already made."},"delivered_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"type":"string"}},"required":["id","endpoint_id","event","idempotency_key","attempt","status","response_status","response_body","error","scheduled_for","delivered_at","created_at"],"additionalProperties":false},"WebhookDeliveryList":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"endpoint_id":{"type":"string"},"event":{"type":"string","enum":["message.received","message.expired","inbox.created","domain.verified","simulator.bounced","simulator.complained"]},"idempotency_key":{"type":"string","description":"Stable across every retry of one event. Dedupe on this: delivery is at-least-once by design."},"attempt":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Attempts made so far. Zero means it has not been tried yet."},"status":{"type":"string","enum":["pending","delivered","failed","exhausted"],"description":"pending (queued or awaiting its next retry), delivered (2xx), failed (attempt failed, more retries to come) or exhausted (retries spent; this event will not arrive)."},"response_status":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"HTTP status your endpoint returned, or null if it never answered."},"response_body":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Truncated. Enough to debug, not enough to store your page."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Why the attempt failed when there was no response at all."},"scheduled_for":{"type":"string","description":"When the next attempt runs, with exponential backoff. In the past for a delivery already made."},"delivered_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"type":"string"}},"required":["id","endpoint_id","event","idempotency_key","attempt","status","response_status","response_body","error","scheduled_for","delivered_at","created_at"],"additionalProperties":false}},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["data","next_cursor"],"additionalProperties":false},"Erasure":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"erased":{"type":"boolean","const":true},"scope":{"type":"string","enum":["message","inbox","address"]},"subject":{"type":"string","description":"What was erased: the id or the address."},"messages_erased":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Rows tombstoned. Zero is a legitimate answer: we held nothing."},"erased_at":{"type":"string"},"audit_event_id":{"type":"string","description":"The audit row this erasure wrote. Quote it in your own records."}},"required":["erased","scope","subject","messages_erased","erased_at","audit_event_id"],"additionalProperties":false},"AuditAction":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","enum":["api_key.created","api_key.revoked","smtp_credential.created","smtp_credential.revoked","domain.added","domain.verified","domain.removed","inbox.purged","message.erased","inbox.erased","address.erased","messages.purged","retention.changed","webhook_endpoint.created","webhook_endpoint.updated","webhook_endpoint.deleted","tenant.suspended","tenant.reinstated","recipient.blocked","recipient.unblocked"],"description":"Closed set. Filter on this rather than parsing a message."},"AuditList":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"action":{"type":"string","enum":["api_key.created","api_key.revoked","smtp_credential.created","smtp_credential.revoked","domain.added","domain.verified","domain.removed","inbox.purged","message.erased","inbox.erased","address.erased","messages.purged","retention.changed","webhook_endpoint.created","webhook_endpoint.updated","webhook_endpoint.deleted","tenant.suspended","tenant.reinstated","recipient.blocked","recipient.unblocked"],"description":"Closed set. Filter on this rather than parsing a message."},"actor":{"type":"string","description":"key_<id> for an API key, user_<id> for a person, system for us."},"subject":{"anyOf":[{"type":"string"},{"type":"null"}]},"project_id":{"anyOf":[{"type":"string","description":"Null on organisation-wide entries such as a suspension."},{"type":"null"}]},"detail":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"occurred_at":{"type":"string"}},"required":["id","action","actor","subject","project_id","detail","occurred_at"],"additionalProperties":false}},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["data","next_cursor"],"additionalProperties":false},"Retention":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"retention_seconds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"messages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Readable messages held right now."},"bytes":{"type":"number","description":"Sum of message sizes as received on the wire."},"oldest_received_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"next_expires_at":{"anyOf":[{"type":"string","description":"When the reaper next deletes something for this project."},{"type":"null"}]},"erased_awaiting_purge":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Tombstoned, invisible to every read, bytes not yet reclaimed."}},"required":["retention_seconds","messages","bytes","oldest_received_at","next_expires_at","erased_awaiting_purge"],"additionalProperties":false},"RetentionChanged":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"retention_seconds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"messages":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Readable messages held right now."},"bytes":{"type":"number","description":"Sum of message sizes as received on the wire."},"oldest_received_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"next_expires_at":{"anyOf":[{"type":"string","description":"When the reaper next deletes something for this project."},{"type":"null"}]},"erased_awaiting_purge":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Tombstoned, invisible to every read, bytes not yet reclaimed."},"previous_retention_seconds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"inboxes_clamped":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Inboxes whose own retention exceeded the new ceiling and was lowered."},"messages_redated":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"audit_event_id":{"type":"string"}},"required":["retention_seconds","messages","bytes","oldest_received_at","next_expires_at","erased_awaiting_purge","previous_retention_seconds","inboxes_clamped","messages_redated","audit_event_id"],"additionalProperties":false},"Region":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"region":{"type":"string","enum":["us","eu"]},"region_name":{"type":"string"},"stays_in_region":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"},"detail":{"type":"string","description":"Where it is stored and why, in a sentence you can quote."}},"required":["category","detail"],"additionalProperties":false},"description":"Message bodies, attachments and everything derived from them."},"may_leave_region":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string"},"detail":{"type":"string","description":"Where it is stored and why, in a sentence you can quote."}},"required":["category","detail"],"additionalProperties":false},"description":"Account records, billing and operational telemetry. Enumerated rather than omitted, because a vague claim here is the one that becomes a breach notification."},"invariants":{"type":"array","items":{"type":"string"}}},"required":["region","region_name","stays_in_region","may_leave_region","invariants"],"additionalProperties":false},"AbuseReport":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"brand":{"type":"string"},"contact":{"type":"string"},"region":{"type":"string","enum":["us","eu"]},"service":{"type":"string"},"outbound_delivery":{"type":"string","const":"none","description":"There is no path in this service that delivers mail to a third party, so it cannot be the source of anything you received."},"controls":{"type":"array","items":{"type":"string"}},"takedown":{"type":"object","properties":{"contact":{"type":"string"},"acknowledgement_hours":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"action_hours":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"include":{"type":"array","items":{"type":"string"}}},"required":["contact","acknowledgement_hours","action_hours","include"],"additionalProperties":false},"receiving":{"type":"object","properties":{"mx_host":{"type":"string"},"shared_inbox_domain":{"type":"string"},"capture_relay_host":{"type":"string"}},"required":["mx_host","shared_inbox_domain","capture_relay_host"],"additionalProperties":false}},"required":["brand","contact","region","service","outbound_delivery","controls","takedown","receiving"],"additionalProperties":false,"description":"Unauthenticated. Served at /.well-known/inboxspec-abuse so an abuse desk can find the contact and the takedown path without an account."},"Health":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","const":"ok"},"brand":{"type":"string"},"region":{"type":"string","enum":["us","eu"]},"time":{"type":"string"}},"required":["status","brand","region","time"],"additionalProperties":false}}}}