{"openapi":"3.1.0","info":{"title":"OVEYON API","version":"1.0.0","summary":"Transactional email: send by API, receive by signed webhook.","description":"Transactional email infrastructure with both directions over HTTP. API-key (Bearer) authentication with scopes; errors with a stable `error` and a localized `message` — including what is refused before the route runs: `invalid_json` (400), `payload_too_large` (413, with the route `limit`), `unsupported_media_type` (415), `internal_error` (500, the detail never leaves our logs) and `not_found` for a route that does not exist; 429 with Retry-After; sandbox with `X-Oveyon-Sandbox: 1`; idempotency with `Idempotency-Key`. Webhooks signed with HMAC-SHA256 (see `webhooks`). LANGUAGE OF HUMAN-READABLE TEXT: like `message`, every field that is a sentence for a person follows the caller — Portuguese when the request comes from Brazil (Cloudflare `CF-IPCountry`), English for everyone else, including when the country is unknown: `detail` of inbound deliveries and of agent-safety signals, `label` of the /v1/stats breakdown, `purpose` and the verification `detail` of domain records, `statusDetail` of sent messages and their deliveries, and the `signature` help returned when a webhook is created. Webhook bodies have no caller: their text (`truncation.reason`, `retro.message`, `quarantine.release.note`, the signals `detail`) follows the ACCOUNT country (Brazil: Portuguese; anywhere else: English). Codes and enums never change language — branch on `error`, `reason`, `status`, `type`, `value` and `window` (whose values are `diária`/`mensal` in every language), never on the text.","contact":{"name":"Oveyon support","email":"support@oveyon.com","url":"https://oveyon.com/support"},"termsOfService":"https://oveyon.com/terms"},"externalDocs":{"description":"Human-readable reference","url":"https://api.oveyon.com/docs"},"servers":[{"url":"https://api.oveyon.com/v1"}],"tags":[{"name":"Meta"},{"name":"Sending"},{"name":"Messages"},{"name":"Utilities"},{"name":"Inbound"},{"name":"Suppressions"},{"name":"Domains"},{"name":"Webhooks"},{"name":"Allow/Block lists"},{"name":"IP rules"},{"name":"Templates"},{"name":"Send policies"},{"name":"NPS/CSAT surveys"},{"name":"Logs"}],"paths":{"/ping":{"get":{"operationId":"get_ping","tags":["Meta"],"summary":"Ping","description":"Shallow: 200 if the API answers. Public. For external health checks.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"service":{"type":"string"},"version":{"type":"string"}}},"example":{"ok":true,"service":"oveyon","version":"v1"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[]}},"/openapi.json":{"get":{"operationId":"get_openapi_json","tags":["Meta"],"summary":"This document","description":"The OpenAPI 3.1 of this API, generated from the code. Public. `?spec=3.0` returns the 3.0.3 variant, for importers that do not read 3.1.","parameters":[{"name":"spec","in":"query","schema":{"type":"string","enum":["3.0"]}}],"responses":{"200":{"description":"OpenAPI document","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"required":["openapi","info","paths"]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"security":[]}},"/send":{"post":{"operationId":"post_send","tags":["Sending"],"summary":"Send a message","description":"Queues a message for up to 5 recipients (to + cc + bcc). Each recipient is one quota unit. `Idempotency-Key` (header) or `idempotencyKey` (body) deduplicates safely; `X-Oveyon-Sandbox: 1` accepts and freezes without delivering. On a 500, only retry with an idempotencyKey.","parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"}},{"name":"X-Oveyon-Sandbox","in":"header","schema":{"type":"string","enum":["1"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendRequest"},"example":{"from":"agent@yourdomain.com","to":"customer@example.com","subject":"Your order 8812 is on its way","text":"Hi — it shipped today."}}}},"responses":{"200":{"description":"Duplicate: the idempotencyKey was already used; returns the original id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuplicateResponse"}}}},"202":{"description":"Accepted (`accepted`) or frozen in sandbox (`sandbox`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResponse"},"example":{"id":"3f8a1c2e-9b4d-4e10-8a77-2b0c9d5e1f34","status":"accepted"}}}},"400":{"description":"bad_request, too_many_recipients, bad_recipient, too_many_attachments, bad_attachment, template_conflict, template_var_missing.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"description":"attachments_too_large (attachments above 15 MB), or payload_too_large (the request body above 25 MB, with `limit`).","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"domain_not_verified, domain_on_hold (sending from the domain is held for review: recently registered, listed on the Spamhaus DBL, or flagged by the reputation screening — the message says which and what to do), domain_not_allowed_for_credential (the key is limited to selected domains of the account and the `from` domain is not one of them — adjust it under Credentials; the domain itself is fine), invalid_recipient_domain, recipient_suppressed, recipient_blocked, recipient_not_allowed, policy_refused, unsub_footer_multi_recipient, template_not_found and template render errors.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"injection_failed — the message could not be queued; the quota is refunded, retry with the same idempotencyKey. Or ip_rules_unavailable.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}}},"security":[{"bearer":[]}],"x-oveyon-scope":"send"}},"/messages":{"get":{"operationId":"get_messages","tags":["Messages"],"summary":"List sent messages","parameters":[{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"},{"name":"status","in":"query","schema":{"type":"string","enum":["accepted","queued","delivered","deferred","bounced","suppressed","failed","frozen"]}},{"name":"outcome","in":"query","schema":{"type":"string","enum":["devolvidas","bloqueadas","fila","held","entregues"]},"description":"Grouped outcome (the dashboard’s five cuts)."},{"name":"recipient","in":"query","schema":{"type":"string"},"description":"Exact address, in to/cc/bcc."},{"name":"domain","in":"query","schema":{"type":"string"},"description":"Numeric id or name."},{"name":"credential","in":"query","schema":{"type":"integer"}},{"$ref":"#/components/parameters/from"},{"$ref":"#/components/parameters/to"},{"name":"event","in":"query","schema":{"type":"string"},"description":"Filter by a timeline event (e.g. opened)."}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:messages"}},"/messages/{uuid}":{"get":{"operationId":"get_messages_uuid","tags":["Messages"],"summary":"Message detail and timeline","parameters":[{"name":"uuid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Message","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/MessageNotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:messages"}},"/stats":{"get":{"operationId":"get_stats","tags":["Messages"],"summary":"Aggregate statistics","parameters":[{"name":"group_by","in":"query","schema":{"type":"string","enum":["day","domain","credential"],"default":"day"}},{"$ref":"#/components/parameters/from"},{"$ref":"#/components/parameters/to"},{"name":"breakdown","in":"query","schema":{"type":"string"},"description":"`provider`, `reason` and/or `credential`, comma-separated."}],"responses":{"200":{"description":"Series and rates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stats"}}}},"400":{"description":"bad_breakdown, bad_date","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:stats"}},"/disposable":{"get":{"operationId":"get_disposable","tags":["Utilities"],"summary":"Is the domain disposable?","description":"Public (no key). Only the domain against our list, refreshed daily; does not check whether the mailbox exists. Per-IP cap: 60/h.","parameters":[{"name":"email","in":"query","schema":{"type":"string"}},{"name":"domain","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisposableCheck"},"example":{"domain":"mailinator.com","result":"disposable","disposable":true,"list":{"updatedAt":"2026-08-27T03:00:00Z","domains":118000}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"List unavailable — `result: \"unknown\"`.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}}},"security":[]}},"/inbound":{"get":{"operationId":"get_inbound","tags":["Inbound"],"summary":"List received emails","parameters":[{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"},{"name":"recipient","in":"query","schema":{"type":"string"}},{"name":"sender","in":"query","schema":{"type":"string"},"description":"Matches envelope and header."},{"name":"domain","in":"query","schema":{"type":"string"}},{"name":"dmarc","in":"query","schema":{"type":"string"}},{"name":"has_attachments","in":"query","schema":{"type":"boolean"}},{"name":"outcome","in":"query","schema":{"type":"string"}},{"name":"thread","in":"query","schema":{"type":"string","format":"uuid"},"description":"Only messages of this conversation (see GET /v1/inbound/threads)."},{"name":"agent_safety","in":"query","schema":{"type":"string"},"description":"Agent-safety verdict: clean, suspicious, dangerous or none (= not evaluated yet). Comma-separated to combine, e.g. suspicious,dangerous. Anything else is 400 bad_agent_safety."},{"$ref":"#/components/parameters/from"},{"$ref":"#/components/parameters/to"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound"}},"/inbound/threads":{"get":{"operationId":"get_inbound_threads","tags":["Inbound"],"summary":"List conversations (threads)","description":"Conversations grouped by In-Reply-To/References within your account — including the replies you sent through OVEYON, so a customer answering your reply lands in the same thread. Ordered by last activity. No grouping by subject.","parameters":[{"$ref":"#/components/parameters/limit"},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque; from next_cursor."}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundThreadList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound"}},"/inbound/threads/{id}":{"get":{"operationId":"get_inbound_threads_id","tags":["Inbound"],"summary":"Conversation detail: received messages and replies sent","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Thread with messages (oldest first) and replies","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundThreadDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound"}},"/inbound/routes":{"get":{"operationId":"get_inbound_routes","tags":["Inbound"],"summary":"List mailboxes (inbound routes)","parameters":[{"name":"domain","in":"query","schema":{"type":"string"},"description":"Only this domain (name)."}],"responses":{"200":{"description":"Mailboxes of the account (up to 1000)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRouteList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound"},"post":{"operationId":"post_inbound_routes","tags":["Inbound"],"summary":"Create a mailbox (and attach channels in one call)","description":"Creates `local@domain` (or the catch-all `*@domain`) on a domain of your account, optionally attaching a new webhook and/or existing channels of the same domain. The mailbox is born WITHOUT security choices (agentSafetyMode null, retroScan off): nothing created by API holds mail by default. Limits: 200 mailboxes per domain, 1000 per account (422). Scope write:inbound.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRouteCreate"}}}},"responses":{"201":{"description":"Created — the inline webhook secret is shown once","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRouteCreated"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"domain_not_found or channel_not_found (channel of another domain/account)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"409":{"description":"route_exists","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"route_limit_domain, route_limit_tenant","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound"}},"/inbound/routes/{id}":{"get":{"operationId":"get_inbound_routes_id","tags":["Inbound"],"summary":"Mailbox detail with its channels","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Mailbox","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRoute"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound"},"patch":{"operationId":"patch_inbound_routes_id","tags":["Inbound"],"summary":"Switch a mailbox on/off and attach channels (additive)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRoutePatch"}}}},"responses":{"200":{"description":"Current state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRouteCreated"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound"},"delete":{"operationId":"delete_inbound_routes_id","tags":["Inbound"],"summary":"Remove a mailbox","description":"Mail to it is refused at the MX from then on. Channels stay (they belong to the domain); received messages stay in the history.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"204":{"description":"Removed"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound"}},"/inbound/routes/{id}/channels/{assocId}":{"delete":{"operationId":"delete_inbound_routes_id_channels_associd","tags":["Inbound"],"summary":"Detach a channel from a mailbox","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"assocId","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"204":{"description":"Detached (the channel itself stays)"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"route_not_found or channel_not_found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound"}},"/inbound-policies":{"get":{"operationId":"get_inbound_policies","tags":["Inbound"],"summary":"List inbound policies (IF→THEN on what arrives), in evaluation order","description":"Evaluated top to bottom on every received message; `tag` and `notify` annotate and continue, every other action stops the chain. With the platform switch off (`holdEnabled: false`), hold/mute/restrict_channels become the tag `would_<action>:<name>`. Scope read:inbound-policies.","responses":{"200":{"description":"Policies","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicyList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound-policies"},"post":{"operationId":"post_inbound_policies","tags":["Inbound"],"summary":"Create an inbound policy (born paused, at the end of the order)","description":"Needs write:inbound-policies and read:inbound. Up to 50 per account and 5 conditions per policy. Born PAUSED: check it with the simulator (`policyId` evaluates it even paused), then POST /unpause. Scope write:inbound-policies.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicyInput"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicy"}}}},"400":{"$ref":"#/components/responses/InboundPolicyBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"insufficient_scope — creating or editing a policy needs write:inbound-policies AND read:inbound (a policy on message fields, once active, reveals which messages matched); or any of the common 403s (key_disabled, account_suspended…).","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"409":{"description":"inbound_policy_name_taken","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/InboundPolicyBodyTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"inbound_policy_limit_reached","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound-policies"}},"/inbound-policies/decisions":{"get":{"operationId":"get_inbound_policies_decisions","tags":["Inbound"],"summary":"Decision feed: which policy matched which message (90 days)","description":"Newest first. The message subject and sender come only when the key also has read:inbound. Scope read:inbound-policies.","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"before","in":"query","description":"Cursor: the `nextBefore` of the previous page.","schema":{"type":"integer"}},{"name":"policy","in":"query","description":"Only the decisions of this policy id.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Feed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicyDecisionList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound-policies"}},"/inbound-policies/simulate":{"post":{"operationId":"post_inbound_policies_simulate","tags":["Inbound"],"summary":"Simulate: which policy would match a message (real or synthetic), with the active policies or a draft","description":"Read-only. `message` is a received message id or a synthetic message. Without `policy`/`policyId` the ACTIVE policies are evaluated (a paused one is not); `policy` tests an unsaved draft; `policyId` tests one saved policy even if paused. On a received message the key needs read:inbound too (it reads the mailbox). Every simulate call — synthetic or not — counts against the per-key inbound read limit (120/min by default): over it, 429 rate_limited with `Retry-After: 60`. That is longer than the 30 s the official SDKs wait by default, so they do NOT retry it: the 429 reaches your code, with the Retry-After — wait and call again, or raise the SDK retry-after cap. The answer includes what the engine SAW. The request body is capped at 256 KB (413). Scope read:inbound-policies.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicySimulateRequest"}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicySimulateResult"}}}},"400":{"$ref":"#/components/responses/InboundPolicyBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"insufficient_scope — a received message needs read:inbound too (it reads the mailbox); or any of the common 403s (key_disabled, account_suspended…). 403 is not a limit: do not retry.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"404":{"description":"inbound_policy_message_not_found, or inbound_policy_not_found for policyId","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"409":{"description":"inbound_policy_unreadable — the saved policy in `policyId` can no longer be read by the engine (it is skipped at arrival); `reason` names the validation that failed. Edit the policy to fix it.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/InboundPolicyBodyTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"description":"rate_limited — the per-key inbound read limit, with `Retry-After: 60` (above the SDKs' default 30 s cap, so it is not retried for you); or the per-IP limit.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound-policies"}},"/inbound-policies/{id}":{"get":{"operationId":"get_inbound_policies_id","tags":["Inbound"],"summary":"Inbound policy detail","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Policy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicy"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/InboundPolicyNotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound-policies"},"put":{"operationId":"put_inbound_policies_id","tags":["Inbound"],"summary":"Replace an inbound policy (optional optimistic concurrency with `version`)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicyInput"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicy"}}}},"400":{"$ref":"#/components/responses/InboundPolicyBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"insufficient_scope — creating or editing a policy needs write:inbound-policies AND read:inbound (a policy on message fields, once active, reveals which messages matched); or any of the common 403s (key_disabled, account_suspended…).","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"404":{"$ref":"#/components/responses/InboundPolicyNotFound"},"409":{"description":"inbound_policy_version_conflict or inbound_policy_name_taken","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/InboundPolicyBodyTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound-policies"},"delete":{"operationId":"delete_inbound_policies_id","tags":["Inbound"],"summary":"Delete an inbound policy","description":"The decision feed keeps the frozen name; messages keep the policy they got.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"204":{"description":"Deleted"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/InboundPolicyNotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound-policies"}},"/inbound-policies/{id}/pause":{"post":{"operationId":"post_inbound_policies_id_pause","tags":["Inbound"],"summary":"Pause an inbound policy","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Paused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicy"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/InboundPolicyNotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound-policies"}},"/inbound-policies/{id}/unpause":{"post":{"operationId":"post_inbound_policies_id_unpause","tags":["Inbound"],"summary":"Activate an inbound policy","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Active","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicy"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/InboundPolicyNotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound-policies"}},"/inbound-policies/reorder":{"post":{"operationId":"post_inbound_policies_reorder","tags":["Inbound"],"summary":"Reorder inbound policies","description":"The ids you send come first, in that order; the ones you leave out keep their relative order after them. Ids of another account are ignored.","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["order"],"properties":{"order":{"type":"array","items":{"type":"integer"}}}}}}},"responses":{"200":{"description":"New order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundPolicyList"}}}},"400":{"$ref":"#/components/responses/InboundPolicyBadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/InboundPolicyBodyTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound-policies"}},"/holds":{"get":{"operationId":"get_holds","tags":["Messages"],"summary":"Drafts awaiting human approval (hold:true)","description":"Scope approve:holds. `status` defaults to pending; also approved, rejected, expired (most recent first, up to 100).","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["pending","approved","rejected","expired"]}},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Drafts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"approve:holds"}},"/messages/{uuid}/approve":{"post":{"operationId":"post_messages_uuid_approve","tags":["Messages"],"summary":"Approve a held draft — it goes out now, without re-signing","description":"Scope approve:holds. The first decision wins: a second call answers 409 already_decided with the decision. Recipients that entered a suppression list while the draft waited are skipped (listed in `skipped`); if none can go out, 409 release_failed and the draft stays pending. Counts warm-up like any outbound mail.","parameters":[{"name":"uuid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Approved and queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldDecision"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"already_decided or release_failed","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"approve:holds"}},"/messages/{uuid}/reject":{"post":{"operationId":"post_messages_uuid_reject","tags":["Messages"],"summary":"Reject a held draft — it never goes out; the quota is refunded","description":"Scope approve:holds. Optional `reason` (up to 500 characters) travels in the `rejected` webhook and in the timeline.","parameters":[{"name":"uuid","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","maxLength":500}}}}}},"responses":{"200":{"description":"Rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldDecision"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"already_decided","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"approve:holds"}},"/inbound/{uid}/release":{"post":{"operationId":"post_inbound_uid_release","tags":["Inbound"],"summary":"Release a held message — by the agent-safety verdict or by an inbound policy (a human decides)","description":"Dispatches now, to the mailbox channels (respecting a restrict_channels the policy recorded — if it would leave a recipient without channels it is dropped, with the tag `would_restrict_channels:<name>`), a message that was held at arrival — with the verdict and `message.quarantine` (heldAt, releasedAt, releasedBy) in the payload. Scope write:inbound: release is an operator gesture, never a tool of the model (the key that replies does not release). Records who released (`api:<key prefix>`). Without a body it releases EVERY held copy; `{ \"recipient\": \"<mailbox>\" }` releases only the copy of that mailbox (each recipient is held by its own mailbox choices and policies).","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"recipient":{"type":"string","maxLength":320,"description":"Release only the copy of this mailbox (one of `recipients[].to`)."}}}}}},"responses":{"200":{"description":"Released","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundReleaseResult"}}}},"400":{"description":"bad_recipient","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"not_quarantined — no copy of the message is held; recipient_not_quarantined — the copy of that mailbox is not held","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"410":{"description":"copy_expired — the archived copy is gone; nothing can be dispatched","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"no_mailbox, or recipient_not_in_message — the message was not sent to that mailbox","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:inbound"}},"/inbound/stats":{"get":{"operationId":"get_inbound_stats","tags":["Inbound"],"summary":"Agent-safety numbers: received, evaluated, verdicts, held, released","description":"The same numbers the panel card shows, by day or by recipient, over a window of up to 92 days (default: the last 30). A message to two mailboxes counts once per day and once per recipient. The engine canary never enters these numbers.","parameters":[{"name":"group_by","in":"query","schema":{"type":"string","enum":["day","recipient"]}},{"$ref":"#/components/parameters/from"},{"$ref":"#/components/parameters/to"}],"responses":{"200":{"description":"Series","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundStats"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound"}},"/inbound/{uid}/reply":{"post":{"operationId":"post_inbound_uid_reply","tags":["Inbound"],"summary":"Reply to a received email (the agent answers)","description":"Sends a plain-text reply FROM the mailbox that received the message TO its Reply-To/From, threaded (In-Reply-To/References) and recorded in the conversation. Counts one quota unit and consumes warm-up like any outbound mail. Loop guard: the reply carries `Auto-Submitted: auto-replied`; replying to automated mail (Auto-Submitted, Precedence bulk/list/junk, null sender) is refused with 422 auto_submitted; at most 5 replies per message and 10 automated replies per conversation per hour (429). A message held by the agent-safety verdict answers 409 message_quarantined until a human releases it — the reply is never the tool that releases. When the message went to several mailboxes and only some copies are held (`partial`), the reply goes out from the first mailbox whose copy was delivered (or the one in `from`).","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundReplyRequest"}}}},"responses":{"200":{"description":"Duplicate idempotency key — the earlier reply id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuplicateResponse"}}}},"202":{"description":"Accepted and queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundReplyAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"sending_disabled, or the key lacks reply:inbound (`insufficient_scope`); or any of the common 403s (key_disabled, account_suspended…). 403 is not a limit: do not retry.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"message_quarantined (every copy held; with agentSafety and release.panel), recipient_quarantined (the copy of the mailbox in `from` is held) or message_blocked","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"410":{"description":"copy_expired — the message was held and its retention expired (policy holds follow the mailbox retention): the copy is gone, it can no longer be released or answered","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"description":"body_too_long (the text above 20000 characters, with `limit`), or payload_too_large (the request body above 25 MB).","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"no_reply_address, domain_not_registered, domain_not_allowed_for_credential (the key is limited to selected domains of the account and the mailbox domain is not one of them — adjust it under Credentials or reply with another key), auto_submitted, recipient_blocked, recipient_not_allowed, from_not_recipient","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"description":"rate_limited (the per-key reply limit, with `Retry-After: 60`; or the per-credential limit), reply_limit (per message), thread_reply_limit (per conversation), quota_exceeded or hold_limit. Read `error` before retrying: reply_limit and thread_reply_limit are the loop guard, not congestion.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"injection_failed or record_failed — the reply could not be queued or recorded; retry with the same Idempotency-Key. Or ip_rules_unavailable.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}}},"security":[{"bearer":[]}],"x-oveyon-scope":"reply:inbound"}},"/inbound/{uid}":{"get":{"operationId":"get_inbound_uid","tags":["Inbound"],"summary":"Received email detail","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Message with attachment manifest and links","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound"}},"/inbound/{uid}/content":{"get":{"operationId":"get_inbound_uid_content","tags":["Inbound"],"summary":"Parsed body (text/html)","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Body, cut at 256 KB per field","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundContent"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound"}},"/inbound/{uid}/raw":{"get":{"operationId":"get_inbound_uid_raw","tags":["Inbound"],"summary":"The original .eml","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"message/rfc822, byte for byte","content":{"message/rfc822":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound"}},"/inbound/{uid}/attachments/{n}":{"get":{"operationId":"get_inbound_uid_attachments_n","tags":["Inbound"],"summary":"Attachment bytes","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string"}},{"name":"n","in":"path","required":true,"schema":{"type":"integer"},"description":"The manifest `ord`, not the file name."}],"responses":{"200":{"description":"The attachment","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:inbound"}},"/suppressions":{"get":{"operationId":"get_suppressions","tags":["Suppressions"],"summary":"List suppressions","parameters":[{"name":"email","in":"query","schema":{"type":"string"},"description":"Substring match."},{"name":"reason","in":"query","schema":{"type":"string","enum":["hard_bounce","complaint","manual","unsubscribe"]}},{"name":"limit","in":"query","schema":{"type":"integer","maximum":500,"default":100}},{"name":"offset","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"List","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionList"}}}},"400":{"description":"bad_request — reason invalid","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:suppressions"},"post":{"operationId":"post_suppressions","tags":["Suppressions"],"summary":"Suppress an address","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"reason":{"type":"string","enum":["manual","unsubscribe"]}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Suppression"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:suppressions"}},"/suppressions/{email}":{"delete":{"operationId":"delete_suppressions_email","tags":["Suppressions"],"summary":"Remove a suppression","description":"A complaint (`complaint`) can never be removed — not even here.","parameters":[{"name":"email","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removed","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:suppressions"}},"/domains":{"get":{"operationId":"get_domains","tags":["Domains"],"summary":"List sending domains","responses":{"200":{"description":"List","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:domains"},"post":{"operationId":"post_domains","tags":["Domains"],"summary":"Add a domain","description":"Returns the DNS records to publish (SPF, DKIM, DMARC). On the Free plan the domain goes through reputation screening; refusal = 403 domain_not_allowed_free, without a reason (on purpose).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string"}}}}}},"responses":{"201":{"description":"Created, with the records","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"400":{"description":"bad_domain","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"domain_not_allowed_free — a free-mail domain (gmail.com…) cannot be registered; or any of the common 403s (key_disabled, account_suspended…).","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"409":{"description":"domain_exists (already yours) or domain_taken (belongs to another account).","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"domain_limit_reached — the plan's domain cap is reached (Free: 1). Remove a domain you no longer use, or contact support to raise the limit.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:domains"}},"/domains/{id}/verify":{"post":{"operationId":"post_domains_id_verify","tags":["Domains"],"summary":"Verify DNS live","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Result per check; `inconclusive` when the resolver did not answer (previous state preserved).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:domains"}},"/webhooks":{"get":{"operationId":"get_webhooks","tags":["Webhooks"],"summary":"List delivery webhooks","responses":{"200":{"description":"List","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"manage:webhooks"},"post":{"operationId":"post_webhooks","tags":["Webhooks"],"summary":"Create a delivery webhook","description":"Events: delivered, bounced, opened, clicked, complained, blocked. The `secret` is shown once. Every POST carries x-oveyon-event, x-oveyon-timestamp and x-oveyon-signature (sha256=HMAC-SHA256(secret, timestamp + \".\" + body)). Internal/private destinations are refused.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"credential":{"type":"object","properties":{"type":{"type":"string","enum":["smtp","api"]},"id":{"type":"integer"}}}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreated"}}}},"400":{"description":"bad_request (url missing), bad_credential or bad_events","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"credential_not_found — not on this account","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"unsafe_url — the URL points to an internal or reserved address","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"manage:webhooks"}},"/webhooks/{id}":{"patch":{"operationId":"patch_webhooks_id","tags":["Webhooks"],"summary":"Update a webhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"description":"bad_request (id or url invalid, nothing to update) or bad_events","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"unsafe_url — the URL points to an internal or reserved address","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"manage:webhooks"},"post":{"operationId":"post_webhooks_id","tags":["Webhooks"],"summary":"Update a webhook (alias of PATCH)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"description":"bad_request (id or url invalid, nothing to update) or bad_events","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"unsafe_url — the URL points to an internal or reserved address","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"manage:webhooks"},"delete":{"operationId":"delete_webhooks_id","tags":["Webhooks"],"summary":"Delete a webhook","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"manage:webhooks"}},"/policies":{"get":{"operationId":"get_policies","tags":["Allow/Block lists"],"summary":"List allow/block entries","parameters":[{"name":"scope","in":"query","schema":{"type":"string","enum":["outbound","inbound"]}},{"name":"kind","in":"query","schema":{"type":"string","enum":["allow","block"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"offset","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"List (a paused entry is still listed and does not apply).","content":{"application/json":{"schema":{"type":"object","properties":{"policies":{"type":"array","items":{"$ref":"#/components/schemas/ListEntry"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"400":{"description":"bad_request — scope or kind invalid","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:policies"},"post":{"operationId":"post_policies","tags":["Allow/Block lists"],"summary":"Create an allow/block entry","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scope","kind","pattern"],"properties":{"scope":{"type":"string","enum":["outbound","inbound"]},"kind":{"type":"string","enum":["allow","block"]},"pattern":{"type":"string"},"domainId":{"type":"integer","description":"Omit (or 0) for every domain of the account."},"credentialType":{"type":"string","enum":["none","smtp","apiKey"],"description":"Omit (or `none`) for every credential."},"credentialId":{"type":"integer"},"comment":{"type":"string","maxLength":200}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEntry"}}}},"400":{"description":"bad_request (scope, kind, pattern or domainId invalid) or bad_credential","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"domain_not_found or credential_not_found — not on this account","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"409":{"description":"policy_exists","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"policy_limit_reached — 5000 entries per list (scope × kind)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:policies"}},"/policies/{id}":{"delete":{"operationId":"delete_policies_id","tags":["Allow/Block lists"],"summary":"Delete an entry","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Removed","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"integer"},"pattern":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:policies"}},"/credentials/ip-rules":{"get":{"operationId":"get_credentials_ip_rules","tags":["IP rules"],"summary":"List CIDRs per credential","responses":{"200":{"description":"Rules (with paused/pausedAt)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"bad_credential — the filter needs credentialType AND credentialId","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:policies"},"post":{"operationId":"post_credentials_ip_rules","tags":["IP rules"],"summary":"Allow a CIDR for a credential","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["credentialType","credentialId","cidr"],"properties":{"credentialType":{"type":"string","enum":["smtp","apiKey"]},"credentialId":{"type":"integer"},"cidr":{"type":"string"}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IpRule"}}}},"400":{"description":"bad_credential or bad_request (CIDR invalid)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"credential_not_found — not on this account","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"409":{"description":"ip_rule_exists — this CIDR is already registered for this credential","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:policies"}},"/credentials/ip-rules/{id}":{"delete":{"operationId":"delete_credentials_ip_rules_id","tags":["IP rules"],"summary":"Remove a CIDR","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Removed","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"integer"},"cidr":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:policies"}},"/credentials/ip-rules/pause":{"post":{"operationId":"post_credentials_ip_rules_pause","tags":["IP rules"],"summary":"Pause a credential’s IP fence","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["credentialType","credentialId"],"properties":{"credentialType":{"type":"string","enum":["smtp","apiKey"]},"credentialId":{"type":"integer"}}}}}},"responses":{"200":{"description":"Paused — authenticates from any IP; the ranges stay registered.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"bad_credential","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"credential_not_found — not on this account","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"409":{"description":"no_ip_rules — this credential has no IP range to pause or re-enable","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:policies"}},"/credentials/ip-rules/unpause":{"post":{"operationId":"post_credentials_ip_rules_unpause","tags":["IP rules"],"summary":"Re-enable the fence","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["credentialType","credentialId"],"properties":{"credentialType":{"type":"string","enum":["smtp","apiKey"]},"credentialId":{"type":"integer"}}}}}},"responses":{"200":{"description":"Re-enabled; may carry `warning.uncoveredRecentIps`.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"bad_credential","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"credential_not_found — not on this account","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"409":{"description":"no_ip_rules — this credential has no IP range to pause or re-enable","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:policies"}},"/templates":{"get":{"operationId":"get_templates","tags":["Templates"],"summary":"List templates","responses":{"200":{"description":"List (light: no content)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:templates"},"post":{"operationId":"post_templates","tags":["Templates"],"summary":"Create a template (draft v1)","description":"Closed syntax: `{{var}}`, `{{{var}}}`, `{{#each}}`, `| \"default\"`. Helpers, partials and comments are refused (400 template_parse_error).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateInput"}}}},"responses":{"201":{"description":"Created — draft v1","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateDraft"}}}},"400":{"description":"template_invalid (with `field`), template_parse_error (with `tag`), template_source_too_large (with `limit`)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"template_name_taken","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:templates"}},"/templates/{ref}":{"get":{"operationId":"get_templates_ref","tags":["Templates"],"summary":"A template with all its versions","parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"},"description":"Numeric id or `name`."}],"responses":{"200":{"description":"Template","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"template_not_found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:templates"},"put":{"operationId":"put_templates_ref","tags":["Templates"],"summary":"New version (draft)","description":"Saves new CONTENT as a draft: if the newest version is already a draft it is overwritten, otherwise a new version is created. The name never changes here. Publish with POST /publish.","parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateVersionInput"}}}},"responses":{"200":{"description":"Draft saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateDraft"}}}},"400":{"description":"template_invalid (with `field`), template_parse_error (with `tag`), template_source_too_large (with `limit`)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"template_not_found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:templates"},"delete":{"operationId":"delete_templates_ref","tags":["Templates"],"summary":"Delete a template","parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted, with every version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateDeleted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"template_not_found","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:templates"}},"/templates/{ref}/publish":{"post":{"operationId":"post_templates_ref_publish","tags":["Templates"],"summary":"Publish a version","description":"No body: publishes the newest draft. `{\"version\": N}`: makes that version current (rollback included).","parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"integer","minimum":1,"description":"An integer ≥ 1 (a string of digits, e.g. \"3\", is accepted too). Anything else — true, [3], \"0x3\", 1.5 — is 400 bad_request, never a guess."}}}}}},"responses":{"200":{"description":"Published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplatePublished"}}}},"400":{"description":"bad_request — `version` is not an integer ≥ 1","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"template_not_found (or that version does not exist)","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"409":{"description":"template_no_draft — no body and no draft to publish","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:templates"}},"/send-policies":{"get":{"operationId":"get_send_policies","tags":["Send policies"],"summary":"List policies in evaluation order","responses":{"200":{"description":"All (cap 50), including paused","content":{"application/json":{"schema":{"type":"object","properties":{"policies":{"type":"array","items":{"$ref":"#/components/schemas/SendPolicy"}},"total":{"type":"integer"},"max":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:send-policies"},"post":{"operationId":"post_send_policies","tags":["Send policies"],"summary":"Create a policy (born paused, last in order)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendPolicyInput"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendPolicy"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"send_policy_limit_reached — 50 send policies per account (with `limit`); remove one before creating another.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:send-policies"}},"/send-policies/decisions":{"get":{"operationId":"get_send_policies_decisions","tags":["Send policies"],"summary":"What the policies decided","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"before","in":"query","schema":{"type":"integer"},"description":"The `id` of the last row of the previous page (the response carries `nextBefore`)."}],"responses":{"200":{"description":"Decisions, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"decisions":{"type":"array","items":{"$ref":"#/components/schemas/Decision"}},"nextBefore":{"type":["integer","null"]}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:send-policies"}},"/send-policies/{id}":{"get":{"operationId":"get_send_policies_id","tags":["Send policies"],"summary":"One policy","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Policy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendPolicy"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:send-policies"},"put":{"operationId":"put_send_policies_id","tags":["Send policies"],"summary":"Replace a whole policy","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendPolicyInput"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendPolicy"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:send-policies"},"delete":{"operationId":"delete_send_policies_id","tags":["Send policies"],"summary":"Delete a policy","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Removed","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"integer"},"name":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:send-policies"}},"/send-policies/{id}/pause":{"post":{"operationId":"post_send_policies_id_pause","tags":["Send policies"],"summary":"Pause","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Paused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendPolicy"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:send-policies"}},"/send-policies/{id}/unpause":{"post":{"operationId":"post_send_policies_id_unpause","tags":["Send policies"],"summary":"Activate","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Active","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendPolicy"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:send-policies"}},"/send-policies/reorder":{"post":{"operationId":"post_send_policies_reorder","tags":["Send policies"],"summary":"Reorder","description":"Foreign ids are ignored; missing ones go to the end in their previous order.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["order"],"properties":{"order":{"type":"array","items":{"type":"integer"}}}}}}},"responses":{"200":{"description":"New order","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:send-policies"}},"/surveys":{"get":{"operationId":"get_surveys","tags":["NPS/CSAT surveys"],"summary":"List surveys","responses":{"200":{"description":"List","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:surveys"},"post":{"operationId":"post_surveys","tags":["NPS/CSAT surveys"],"summary":"Create a survey","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SurveyInput"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Survey"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:surveys"}},"/surveys/{id}":{"get":{"operationId":"get_surveys_id","tags":["NPS/CSAT surveys"],"summary":"One survey with its rollup","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Survey + rollup (media, distribuicao, nps, range)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:surveys"},"put":{"operationId":"put_surveys_id","tags":["NPS/CSAT surveys"],"summary":"Replace a survey (kind is ignored)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SurveyInput"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Survey"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:surveys"},"delete":{"operationId":"delete_surveys_id","tags":["NPS/CSAT surveys"],"summary":"Delete a survey and its responses","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Deleted; `responsesDeleted` says how many responses went with it.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"integer"},"name":{"type":"string"},"responsesDeleted":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"write:surveys"}},"/surveys/{id}/responses":{"get":{"operationId":"get_surveys_id_responses","tags":["NPS/CSAT surveys"],"summary":"Responses (newest first) and the rollup","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"before","in":"query","schema":{"type":"integer"},"description":"The `nextBefore` of the previous page (never an offset)."}],"responses":{"200":{"description":"Page; each item is the response’s current state (key by sendId).","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["responses","nextBefore","rollup"],"properties":{"responses":{"type":"array","items":{"$ref":"#/components/schemas/SurveyResponse"}},"nextBefore":{"type":["integer","null"],"description":"Pass it as `before` for the next page; null = last page."},"rollup":{"$ref":"#/components/schemas/SurveyRollup"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:surveys"}},"/surveys/{id}/send":{"post":{"operationId":"post_surveys_id_send","tags":["NPS/CSAT surveys"],"summary":"Send to one recipient","description":"The score is the click in the email. Anti-fatigue: the same recipient is surveyed once per account window (429 recipient_recently_surveyed, with a Retry-After that may be weeks). The fixed text of the email and the voting page follow the account language; your subject and question go out as you wrote them.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to"],"properties":{"to":{"type":"string","format":"email"},"meta":{"type":"object","additionalProperties":true,"description":"Your context (≤ 4 KB); comes back in the webhook and in the responses."},"idempotencyKey":{"type":"string"}}}}}},"responses":{"200":{"description":"Duplicate (idempotencyKey): the id of the ORIGINAL survey send (an integer — not the message id).","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["id","status","message"],"properties":{"id":{"type":"integer"},"status":{"type":"string","enum":["duplicate"]},"message":{"type":"string"}}}}}},"202":{"description":"Sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"422":{"description":"survey_inactive, or any refusal of POST /v1/send (domain_not_verified, domain_on_hold, domain_not_allowed_for_credential, recipient_suppressed, policy_refused…) — the survey goes out as a normal email.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"injection_failed — the message could not be queued (the quota is refunded; retry with the same idempotencyKey), or ip_rules_unavailable.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}}},"security":[{"bearer":[]}],"x-oveyon-scope":"send:surveys"}},"/logs":{"get":{"operationId":"get_logs","tags":["Logs"],"summary":"List the API calls of the account","description":"Every authenticated call your API keys made, newest first, kept for 90 days (the retention period of the log). Request and response bodies are stored REDACTED and capped at 32 KB each; read them in GET /v1/logs/{id}. Calls refused before the key was identified (invalid key, per-IP limits) are not in the log — there is no account to attribute them to. The two log routes are logged too, without the response body. Scope read:logs.","parameters":[{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"},{"name":"status","in":"query","schema":{"type":"string"},"description":"`success` (1xx–3xx), `error` (4xx–5xx), a class (`2xx`, `4xx`, `5xx`) or an exact code (`422`). Anything else is 400 bad_status."},{"name":"credential","in":"query","schema":{"type":"integer"},"description":"The id of one API key of the account."},{"$ref":"#/components/parameters/from"},{"$ref":"#/components/parameters/to"},{"name":"route","in":"query","schema":{"type":"string"},"description":"A route pattern, with or without the method: `POST /v1/send`, `/v1/messages/:uuid` (or `{uuid}`). Malformed is 400 bad_route."},{"name":"sdk","in":"query","schema":{"type":"string","enum":["oveyon-node","oveyon-python","oveyon-php","curl","node","python","php","go","java","ruby","postman","insomnia","browser","other","none"]},"description":"The client family detected from the User-Agent (`none` = calls without User-Agent). Anything else is 400 bad_sdk."}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiLogList"}}}},"400":{"description":"bad_cursor, bad_date, bad_status, bad_credential, bad_route, bad_sdk.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:logs"}},"/logs/{id}":{"get":{"operationId":"get_logs_id","tags":["Logs"],"summary":"One API call with its request and response bodies","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"The call","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiLogDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"security":[{"bearer":[]}],"x-oveyon-scope":"read:logs"}}},"webhooks":{"holdEvent":{"post":{"summary":"Draft approval events (held, approved, rejected, hold_expired) — opt-in per endpoint","parameters":[{"name":"x-oveyon-event","in":"header","required":true,"schema":{"type":"string"}},{"name":"x-oveyon-timestamp","in":"header","required":true,"schema":{"type":"integer"},"description":"Epoch seconds. Reject if |now − timestamp| > 300 s."},{"name":"x-oveyon-signature","in":"header","required":true,"schema":{"type":"string"},"description":"`sha256=` + HMAC-SHA256(secret, timestamp + \".\" + raw body)."}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"event":{"type":"string","enum":["held","approved","rejected","hold_expired"]},"messageId":{"type":"string","format":"uuid"},"recipient":{"type":"string"},"status":{"type":"string","description":"frozen while held; queued after approval; failed after reject/expiry."},"detail":{"type":"object","properties":{"hold":{"type":"object","properties":{"note":{"type":["string","null"]},"requestedBy":{"type":["string","null"]},"expiresAt":{"type":"string","format":"date-time"},"decidedAt":{"type":["string","null"]},"decidedBy":{"type":["string","null"]},"reason":{"type":["string","null"]},"url":{"type":"string","description":"GET /v1/messages/{id} — the body is not in the webhook; the agent already has what it sent."}}}}},"timestamp":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"Answer 2xx fast."}}}},"deliveryEvent":{"post":{"summary":"Delivery event (delivered, bounced, opened, clicked, complained, blocked)","parameters":[{"name":"x-oveyon-event","in":"header","required":true,"schema":{"type":"string"}},{"name":"x-oveyon-timestamp","in":"header","required":true,"schema":{"type":"integer"},"description":"Epoch seconds. Reject if |now − timestamp| > 300 s."},{"name":"x-oveyon-signature","in":"header","required":true,"schema":{"type":"string"},"description":"`sha256=` + HMAC-SHA256(secret, timestamp + \".\" + raw body)."}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"event":{"type":"string"},"id":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}},"additionalProperties":true}}}},"responses":{"200":{"description":"Any 2xx within 10 s counts as delivered; 3xx/4xx/5xx/timeout go to retry (6 attempts)."}}}},"inbound.received":{"post":{"summary":"Email received (per recipient; stable eventId for deduplication)","parameters":[{"name":"x-oveyon-event","in":"header","required":true,"schema":{"type":"string"}},{"name":"x-oveyon-timestamp","in":"header","required":true,"schema":{"type":"integer"},"description":"Epoch seconds. Reject if |now − timestamp| > 300 s."},{"name":"x-oveyon-signature","in":"header","required":true,"schema":{"type":"string"},"description":"`sha256=` + HMAC-SHA256(secret, timestamp + \".\" + raw body)."}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"event":{"type":"string","const":"inbound.received"},"eventId":{"type":"string","format":"uuid"},"schemaVersion":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"message":{"$ref":"#/components/schemas/InboundMessage"},"recipient":{"type":"object","properties":{"id":{"type":"string"},"to":{"type":"string"}}},"attachments":{"type":"array","items":{"type":"object","additionalProperties":true}},"truncation":{"type":"object","properties":{"degraded":{"type":"boolean"},"requestedMode":{"type":"string"},"mode":{"type":"string"},"reason":{"type":["string","null"]},"maxBytes":{"type":"integer"}},"additionalProperties":true},"text":{"type":["string","null"],"description":"Full text body (modes full / full+attachments), capped per field."},"html":{"type":["string","null"],"description":"Third-party HTML — sanitize before rendering."},"replyText":{"type":["string","null"],"description":"The NEW text of the message with quoted history, Outlook/forwarded headers and the RFC 3676 signature removed (pt/en). Feed this to a model, not `text`. Equals `text` when nothing was quoted."},"replyStripped":{"type":"boolean","description":"True when something was removed to produce replyText."},"replyMarkers":{"type":"array","items":{"type":"string","enum":["quote_header","quoted_lines","outlook_header","original_message","forwarded","signature"]},"description":"What was recognized and removed."}},"additionalProperties":true}}}},"responses":{"200":{"description":"Answer 2xx fast; do the heavy work afterwards."}}}},"inbound.retro_flagged":{"post":{"summary":"A message we already delivered turned malicious (a link was listed AFTER delivery)","parameters":[{"name":"x-oveyon-event","in":"header","required":true,"schema":{"type":"string"}},{"name":"x-oveyon-timestamp","in":"header","required":true,"schema":{"type":"integer"},"description":"Epoch seconds. Reject if |now − timestamp| > 300 s."},{"name":"x-oveyon-signature","in":"header","required":true,"schema":{"type":"string"},"description":"`sha256=` + HMAC-SHA256(secret, timestamp + \".\" + raw body)."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundRetroFlagged"},"description":"Same schema as components.schemas.InboundRetroFlagged — one definition, one truth."}}},"responses":{"200":{"description":"Answer 2xx fast. The message itself was NOT changed — it stays delivered; this is a warning, not a recall."}}}},"inbound.quarantined":{"post":{"summary":"The agent-safety verdict held a message BEFORE delivery — we never hold without telling you","parameters":[{"name":"x-oveyon-event","in":"header","required":true,"schema":{"type":"string"}},{"name":"x-oveyon-timestamp","in":"header","required":true,"schema":{"type":"integer"},"description":"Epoch seconds. Reject if |now − timestamp| > 300 s."},{"name":"x-oveyon-signature","in":"header","required":true,"schema":{"type":"string"},"description":"`sha256=` + HMAC-SHA256(secret, timestamp + \".\" + raw body)."}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundQuarantined"},"description":"Same schema as components.schemas.InboundQuarantined — one definition, one truth."}}},"responses":{"200":{"description":"Answer 2xx fast. Nothing was delivered: the body is held until a human releases it in the panel; reply via API answers 409 message_quarantined meanwhile."}}}},"url_verification":{"post":{"summary":"Challenge before an inbound webhook URL is saved","parameters":[{"name":"x-oveyon-event","in":"header","required":true,"schema":{"type":"string"}},{"name":"x-oveyon-timestamp","in":"header","required":true,"schema":{"type":"integer"},"description":"Epoch seconds. Reject if |now − timestamp| > 300 s."},{"name":"x-oveyon-signature","in":"header","required":true,"schema":{"type":"string"},"description":"`sha256=` + HMAC-SHA256(secret, timestamp + \".\" + raw body)."}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","const":"url_verification"},"challenge":{"type":"string"},"url":{"type":"string"},"sentAt":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"Echo the `challenge` value with a 2xx (an empty body does NOT pass). Timeout 10 s."}}}},"survey.response":{"post":{"summary":"Survey response (current state; may arrive more than once per response — key by sendId)","parameters":[{"name":"x-oveyon-event","in":"header","required":true,"schema":{"type":"string"}},{"name":"x-oveyon-timestamp","in":"header","required":true,"schema":{"type":"integer"},"description":"Epoch seconds. Reject if |now − timestamp| > 300 s."},{"name":"x-oveyon-signature","in":"header","required":true,"schema":{"type":"string"},"description":"`sha256=` + HMAC-SHA256(secret, timestamp + \".\" + raw body)."}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"event":{"type":"string","const":"survey.response"},"surveyId":{"type":"integer"},"surveyName":{"type":"string"},"kind":{"type":"string"},"sendId":{"type":"string"},"recipient":{"type":"string"},"score":{"type":"integer"},"comment":{"type":["string","null"]},"updated":{"type":"boolean"},"meta":{"type":"object","additionalProperties":true},"messageId":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}},"additionalProperties":true}}}},"responses":{"200":{"description":"2xx."}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"Account API key: `Authorization: Bearer <key>`. Each key carries scopes; the scope an operation requires is in `x-oveyon-scope`."}},"parameters":{"limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},"cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"The `next_cursor` of the previous page. Cursors from different routes are not interchangeable (400 bad_cursor)."},"from":{"name":"from","in":"query","schema":{"type":"string"},"description":"Start of the range, ISO 8601 (YYYY-MM-DD or timestamp)."},"to":{"name":"to","in":"query","schema":{"type":"string"},"description":"End of the range, ISO 8601."}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until you may retry."}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"example":{"error":"unauthorized"}}}},"Forbidden":{"description":"Insufficient scope (`insufficient_scope`, with `required` and `have`), API key switched off by its owner in the panel (`key_disabled` — the key is valid; turn it back on or use another one, do not rotate), sending disabled, account suspended (`account_suspended`) or unknown (`account_unknown`), or the key used from an IP outside its allowed ranges (`ip_not_allowed`, with the `allowed` list — see /v1/credentials/ip-rules). 403 is not a limit: do not retry.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"example":{"error":"insufficient_scope","required":"send","have":["read:messages"]}}}},"NotFound":{"description":"Does not exist, belongs to another account, or the identifier is malformed — the response is identical in all three cases, on purpose.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"example":{"error":"not_found"}}}},"RateLimited":{"description":"Per-IP limit (`rate_limited`), too many failed authentications from your IP (`too_many_auth_failures` — checked before the route, on every operation), account quota (`quota_exceeded` with `window`, `used`, `limit`), warmup cap (`warmup_cap_reached`) or saturation (`queue_full`). Honor `Retry-After` with exponential backoff and jitter.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"example":{"error":"rate_limited","retryAfter":60}}}},"BadRequest":{"description":"Invalid request — fix it before retrying. Includes `invalid_json`: with `Content-Type: application/json` the body must be valid JSON and cannot be empty.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"example":{"error":"bad_request"}}}},"InboundPolicyBadRequest":{"description":"bad_request with a stable `reason` (the `message` follows your language; the `reason` never changes): name_empty, name_too_long, name_invalid, unknown_action, no_conditions, too_many_conditions, conditions_too_large, invalid_condition, unknown_field, invalid_op, empty_value, value_too_long, invalid_value, too_many_items, too_many_wildcards, params_not_accepted, invalid_params, unknown_param, param_required, param_invalid, unknown_channel, mailboxes_invalid, mailboxes_empty, too_many_mailboxes, unknown_mailbox, invalid_version, invalid_order, message_required, invalid_received_at, invalid_simulate. When the problem is one condition, `field` (and `op`) name it; when it is an action parameter, `param` names it; `max`/`min`/`allowed`/`hint` explain the limit.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"example":{"error":"bad_request","reason":"invalid_value","field":"subject","hint":"matches needs at least one *","message":"invalid condition (subject) — check field, operator and value against the catalog"}}}},"MessageNotFound":{"description":"`not_found` — does not exist, belongs to another account, or the identifier is malformed (identical in all three cases, on purpose). `outside_log_window` — the message is yours but was accepted before your plan's log window (Free 3 days, Starter 15, Plus 20, Growth 30, Business 45, Scale 60, Enterprise 365); `message` says the window. Held messages are never outside it while they wait for a decision. After the platform's own retention period (longer than any plan's window, or the end of yours if yours is longer) the message is deleted and answers `not_found`.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"example":{"error":"outside_log_window","message":"this message is outside your plan's log window (3 days) — the log shows messages accepted since 2026-10-01T04:00:00.000Z"}}}},"InboundPolicyNotFound":{"description":"inbound_policy_not_found — the policy does not exist or belongs to another account (identical on purpose).","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"example":{"error":"inbound_policy_not_found","message":"inbound policy not found in this account"}}}},"InboundPolicyBodyTooLarge":{"description":"payload_too_large — the request body exceeds 256 KB (the inbound-policy routes accept small bodies only), with `limit: 262144`. Refused before any validation. Do not retry the same body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLarge"},"example":{"error":"payload_too_large","message":"request body exceeds this route's limit (262144 bytes)","limit":262144}}}},"PayloadTooLarge":{"description":"payload_too_large — the request body exceeds this route's limit (`limit`, in bytes: 25 MB unless the operation says otherwise). The body is read up to the limit before the key is validated. Do not retry the same body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLarge"},"example":{"error":"payload_too_large","message":"request body exceeds this route's limit (26214400 bytes)","limit":26214400}}}},"UnsupportedMediaType":{"description":"unsupported_media_type — send the body as `application/json`.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"example":{"error":"unsupported_media_type","message":"unsupported content type — send the body as application/json"}}}},"InternalError":{"description":"internal_error — an unexpected failure on our side. The detail stays in our logs, never in the body. Retry reads; retry a write only when it is idempotent or carries an idempotency key.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"example":{"error":"internal_error","message":"internal error — try again; on a sending POST, only with an idempotencyKey"}}}},"ServiceUnavailable":{"description":"ip_rules_unavailable — the IP ranges bound to your key could not be checked right now (caution, not a denial). Retry after `Retry-After` (30 s).","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"example":{"error":"ip_rules_unavailable","message":"network binding verification is temporarily unavailable — retry in a few seconds (this is caution, not a denial)."}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable code to branch on (never compare `message`: it is localized by the caller’s country)."},"message":{"type":"string"}},"additionalProperties":true},"PayloadTooLarge":{"type":"object","required":["error","message","limit"],"additionalProperties":false,"properties":{"error":{"type":"string","enum":["payload_too_large"]},"message":{"type":"string"},"limit":{"type":["integer","null"],"description":"The limit of this route, in bytes."}}},"SignatureDoc":{"type":"object","additionalProperties":false,"required":["header","timestampHeader","verify"],"properties":{"header":{"type":"string"},"timestampHeader":{"type":"string"},"verify":{"type":"string"}}},"Attachment":{"type":"object","required":["filename","content"],"properties":{"filename":{"type":"string"},"content":{"type":"string","description":"Base64-encoded content."},"contentType":{"type":"string"}}},"Hold":{"type":"object","description":"A draft awaiting human approval (sent with hold:true, or held by a sending policy with action reter_para_aprovacao). Approve = goes out as accepted, without re-signing, and counts warm-up; reject = never goes out and the quota is refunded; expired = never goes out (you are notified).","properties":{"id":{"type":"string","format":"uuid","description":"The message id."},"status":{"type":"string","enum":["pending","approved","rejected","expired"]},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"subject":{"type":["string","null"]},"note":{"type":["string","null"],"description":"What the agent told the approver (holdNote)."},"requestedBy":{"type":["string","null"],"description":"`api:<key prefix>` or `policy:<name>`."},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"decidedAt":{"type":["string","null"],"format":"date-time"},"decidedBy":{"type":["string","null"],"description":"`api:<key prefix>`, `portal:<user>`, `telegram:<chat>` or `system:expiry`."},"reason":{"type":["string","null"]},"sizeBytes":{"type":["integer","null"]},"url":{"type":"string"},"approve":{"type":"string"},"reject":{"type":"string"}}},"HoldList":{"type":"object","properties":{"status":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Hold"}}}},"HoldDecision":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["approved","rejected"]},"decidedAt":{"type":"string","format":"date-time"},"decidedBy":{"type":"string"},"reason":{"type":"string"},"recipients":{"type":"array","items":{"type":"string"},"description":"approve: who it went out to."},"skipped":{"type":"array","description":"approve: recipients that entered a suppression list while the draft waited — they were skipped, the rest went out.","items":{"type":"object","properties":{"rcptTo":{"type":"string"},"why":{"type":"string"}}}}}},"SendRequest":{"type":"object","required":["from","to"],"properties":{"hold":{"type":"boolean","description":"F3b — hold the message for HUMAN approval instead of delivering: accepted, signed and stored, not sent. The 202 answers status `held` with approve/reject URLs; the human decides in the portal, by Telegram, or by POST /v1/messages/{id}/approve|reject (scope approve:holds — the key that sends is not the key that approves). Default TTL 24 h, max 7 days; expired drafts never go out. Counts one quota unit per recipient at acceptance; rejected/expired refunds it. Ignored (status `sandbox`) when the key itself is in sandbox."},"holdTtl":{"type":"integer","minimum":300,"maximum":604800,"description":"Seconds until the draft expires (default 86400)."},"holdNote":{"type":"string","maxLength":500,"description":"What the agent wants the approver to know."},"from":{"type":"string","description":"`a@b.com` or `Name <a@b.com>`; the domain must be verified in the account."},"to":{"oneOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"One address or a list. `\"Name <a@b.com>\"` is accepted too."},"cc":{"oneOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"One address or a list. `\"Name <a@b.com>\"` is accepted too."},"bcc":{"oneOf":[{"type":"string","format":"email"},{"type":"array","items":{"type":"string","format":"email"}}],"description":"One address or a list. `\"Name <a@b.com>\"` is accepted too."},"subject":{"type":"string","maxLength":500},"html":{"type":"string"},"text":{"type":"string","description":"At least one of `html`/`text` is required — unless `templateId` is used."},"templateId":{"oneOf":[{"type":"integer"},{"type":"string"}],"description":"Numeric id or `name` of a published template. Mutually exclusive with subject/html/text."},"data":{"type":"object","additionalProperties":true,"description":"Template variables."},"version":{"type":"integer","minimum":1,"description":"Pins a published template version."},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Extra headers. List-Unsubscribe, X-Report-Abuse and X-Oveyon-* are ignored."},"idempotencyKey":{"type":"string","description":"The same key never produces two messages (alternative to the Idempotency-Key header)."},"attachments":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/Attachment"},"description":"Up to 20 attachments, ≤ 15 MB in total."},"sandbox":{"type":"boolean","description":"true accepts and freezes without delivering (or header X-Oveyon-Sandbox: 1)."}}},"SendResponse":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["accepted","sandbox","held"],"description":"`held` = accepted and waiting for human approval (sent with `hold: true`, or held by a sending policy)."},"recipients":{"type":"integer","description":"Present only when there is more than one distinct address."},"hold":{"type":"object","additionalProperties":false,"description":"Only when `held`: until when the draft waits, and the decision routes.","properties":{"expiresAt":{"type":"string","format":"date-time"},"note":{"type":["string","null"]},"requestedBy":{"type":["string","null"]},"approve":{"type":"string"},"reject":{"type":"string"}}}}},"DuplicateResponse":{"type":"object","required":["id","status","message"],"properties":{"id":{"type":"string","format":"uuid","description":"The id of the ORIGINAL message (the one the idempotencyKey created)."},"status":{"type":"string","enum":["duplicate"]},"message":{"type":"string"}}},"Message":{"type":"object","properties":{"id":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"},"subject":{"type":["string","null"]},"status":{"type":"string","enum":["accepted","queued","delivered","deferred","bounced","suppressed","failed","frozen"]},"statusDetail":{"type":["string","null"]},"route":{"type":["string","null"],"description":"Which outbound path carried it; null until routed."},"acceptedAt":{"type":["string","null"],"format":"date-time"},"deliveredAt":{"type":["string","null"],"format":"date-time"},"templateId":{"type":["integer","null"]},"templateVersion":{"type":["integer","null"]}},"additionalProperties":true},"MessageDetail":{"allOf":[{"$ref":"#/components/schemas/Message"},{"type":"object","properties":{"sizeBytes":{"type":["integer","null"]},"deliveries":{"type":"array","items":{"type":"object","additionalProperties":true}},"events":{"type":"array","items":{"type":"object","additionalProperties":true}},"tracking":{"type":"array","description":"Open and click events, oldest first. `readMsEstimate` is a weak estimate (the gap between beacons), opens only.","items":{"type":"object","additionalProperties":false,"properties":{"type":{"type":"string","enum":["open","click"]},"url":{"type":["string","null"]},"host":{"type":["string","null"]},"readMsEstimate":{"type":["integer","null"]},"at":{"type":"string","format":"date-time"}}}}}}]},"MessageList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"next_cursor":{"type":["string","null"]},"log_window":{"type":["object","null"],"description":"Your plan's log window: the list only shows messages accepted since `since` (held messages show until decided). `from` earlier than `since` is not an error — the page starts where the log starts. null = no plan window (the platform's own retention period still applies).","properties":{"days":{"type":"integer"},"since":{"type":"string","format":"date-time"}}}}},"ApiLog":{"type":"object","additionalProperties":false,"required":["id","createdAt","method","route","path","status","durationMs","credential","ip","userAgent","sdk","error","messageId"],"properties":{"id":{"type":"integer","description":"The log entry id (GET /v1/logs/{id})."},"createdAt":{"type":"string","format":"date-time","description":"When the request ARRIVED (milliseconds)."},"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE"]},"route":{"type":"string","description":"The route PATTERN, the same for every id (`/v1/messages/:uuid`). Filter by it."},"path":{"type":"string","description":"The path as called, with the ids and without the query string."},"status":{"type":"integer","description":"The HTTP status we answered."},"durationMs":{"type":"integer","description":"Server-side time until the response was ready."},"credential":{"type":"object","additionalProperties":false,"required":["id","name","prefix"],"description":"The API key that made the call (`name` and `prefix` are null once the key is deleted).","properties":{"id":{"type":"integer"},"name":{"type":["string","null"]},"prefix":{"type":["string","null"]}}},"ip":{"type":["string","null"],"description":"The caller IP as seen by our edge."},"userAgent":{"type":["string","null"],"description":"The User-Agent header (up to 255 characters), with any key-shaped value redacted."},"sdk":{"type":["object","null"],"additionalProperties":false,"required":["name","version"],"description":"The client family detected from the User-Agent; null = no User-Agent. `other` = not recognized.","properties":{"name":{"type":"string","enum":["oveyon-node","oveyon-python","oveyon-php","curl","node","python","php","go","java","ruby","postman","insomnia","browser","other"]},"version":{"type":["string","null"]}}},"error":{"type":["string","null"],"description":"The `error` code of the response, on 4xx/5xx."},"messageId":{"type":["string","null"],"format":"uuid","description":"The message this call created (POST /v1/send, reply, survey send) or acted on (GET/approve/reject of /v1/messages/{uuid})."}}},"ApiLogDetail":{"type":"object","additionalProperties":false,"required":["id","createdAt","method","route","path","status","durationMs","credential","ip","userAgent","sdk","error","messageId","query","request","response"],"properties":{"id":{"type":"integer","description":"The log entry id (GET /v1/logs/{id})."},"createdAt":{"type":"string","format":"date-time","description":"When the request ARRIVED (milliseconds)."},"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE"]},"route":{"type":"string","description":"The route PATTERN, the same for every id (`/v1/messages/:uuid`). Filter by it."},"path":{"type":"string","description":"The path as called, with the ids and without the query string."},"status":{"type":"integer","description":"The HTTP status we answered."},"durationMs":{"type":"integer","description":"Server-side time until the response was ready."},"credential":{"type":"object","additionalProperties":false,"required":["id","name","prefix"],"description":"The API key that made the call (`name` and `prefix` are null once the key is deleted).","properties":{"id":{"type":"integer"},"name":{"type":["string","null"]},"prefix":{"type":["string","null"]}}},"ip":{"type":["string","null"],"description":"The caller IP as seen by our edge."},"userAgent":{"type":["string","null"],"description":"The User-Agent header (up to 255 characters), with any key-shaped value redacted."},"sdk":{"type":["object","null"],"additionalProperties":false,"required":["name","version"],"description":"The client family detected from the User-Agent; null = no User-Agent. `other` = not recognized.","properties":{"name":{"type":"string","enum":["oveyon-node","oveyon-python","oveyon-php","curl","node","python","php","go","java","ruby","postman","insomnia","browser","other"]},"version":{"type":["string","null"]}}},"error":{"type":["string","null"],"description":"The `error` code of the response, on 4xx/5xx."},"messageId":{"type":["string","null"],"format":"uuid","description":"The message this call created (POST /v1/send, reply, survey send) or acted on (GET/approve/reject of /v1/messages/{uuid})."},"query":{"type":["object","null"],"additionalProperties":true,"description":"The query string as received, redacted by the same rules as the bodies. null = no query string."},"request":{"$ref":"#/components/schemas/ApiLogBody"},"response":{"$ref":"#/components/schemas/ApiLogBody"}}},"ApiLogBody":{"type":"object","additionalProperties":false,"required":["body","bytes","truncated"],"description":"A body as stored: TEXT (usually JSON), with secrets replaced by «redigido» and message bodies and attachments (`html`, `text`, `content`…) replaced by `{ \"omitted\": \"content\", \"length\": N }`, and template variables (`data`) keeping their names but not their values. Inbound content routes and the log routes store only `{ \"omitted\": …, \"bytes\": N }`. Request headers are never stored.","properties":{"body":{"type":["string","null"],"description":"null = the call had no body."},"bytes":{"type":["integer","null"],"description":"The ORIGINAL size in bytes, when known."},"truncated":{"type":"boolean","description":"true when the stored text was cut at the 32 KB cap — it may then no longer be valid JSON."}}},"ApiLogList":{"type":"object","additionalProperties":false,"required":["data","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiLog"}},"next_cursor":{"type":["string","null"],"description":"null = last page."}}},"Stats":{"type":"object","properties":{"series":{"type":"array","items":{"type":"object","additionalProperties":true}},"rates":{"type":"object","additionalProperties":true},"breakdown":{"type":"object","additionalProperties":true}},"additionalProperties":true},"DisposableCheck":{"type":"object","properties":{"domain":{"type":"string"},"result":{"type":"string","enum":["disposable","not_listed","unknown"]},"disposable":{"type":["boolean","null"]},"list":{"type":"object","properties":{"updatedAt":{"type":"string"},"domains":{"type":"integer"},"source":{"type":"string"}}}}},"Authentication":{"type":"object","description":"null = not evaluated.","properties":{"spf":{"type":["string","null"]},"dkim":{"type":["string","null"]},"dmarc":{"type":["string","null"]}}},"InboundMessage":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"receivedAt":{"type":"string","format":"date-time"},"from":{"type":["string","null"],"description":"Envelope sender (MAIL FROM); `\"\"` is a bounce (null sender)."},"fromHeader":{"type":["string","null"]},"fromName":{"type":["string","null"]},"subject":{"type":["string","null"]},"domain":{"type":["string","null"]},"thread":{"$ref":"#/components/schemas/InboundThreadRef"},"recipients":{"type":"array","description":"One item per mailbox that received the message. Each recipient is judged by the policies and the security quarantine of ITS mailbox: one copy can be delivered while another is held.","items":{"type":"object","properties":{"id":{"type":"string"},"to":{"type":"string"},"outcome":{"type":"string","description":"This copy: `accepted` (delivered) or `quarantined` (held for review). Messages from before per-mailbox policies carry the message outcome here."},"heldBy":{"type":["string","null"],"enum":["agent_safety","policy",null],"description":"Who held this copy (kept after release, as history)."},"policy":{"$ref":"#/components/schemas/InboundPolicyInfo"}},"additionalProperties":true}},"authentication":{"$ref":"#/components/schemas/Authentication"},"spamScore":{"type":["integer","null"],"minimum":0,"maximum":100},"agentSafety":{"$ref":"#/components/schemas/AgentSafety"},"sizeBytes":{"type":"integer"},"attachmentCount":{"type":"integer"},"expiresAt":{"type":["string","null"],"format":"date-time"},"outcome":{"type":"string","description":"`accepted`, `blocked`, `quarantined` (every copy held) or `partial` (some copies delivered, some held — see `recipients[].outcome`) — branch on this, never on blockedReason. `policy` at message level is the first recipient's."},"blockedReason":{"type":["string","null"]},"quarantine":{"$ref":"#/components/schemas/InboundQuarantineInfo"},"policy":{"$ref":"#/components/schemas/InboundPolicyInfo"}},"additionalProperties":true},"InboundThreadRef":{"type":["object","null"],"description":"The conversation this message belongs to (grouped by In-Reply-To/References within your account, including replies you sent through OVEYON). null = not resolved (fail-open) or older than the backfill.","properties":{"id":{"type":"string","format":"uuid","description":"Thread id — GET /v1/inbound/threads/{id}; filter the list with ?thread=."}},"required":["id"]},"InboundThread":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"subject":{"type":["string","null"],"description":"Normalized subject of the first message (Re:/Fwd: stripped, lowercased) — for display only; grouping never uses it."},"firstAt":{"type":"string","format":"date-time"},"lastAt":{"type":"string","format":"date-time","description":"Last received message or reply — the list is ordered by it."},"messageCount":{"type":"integer","description":"Messages received."},"replyCount":{"type":"integer","description":"Replies sent through OVEYON (portal, chat, API)."},"url":{"type":"string"}},"required":["id","firstAt","lastAt","messageCount","replyCount"]},"InboundThreadList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InboundThread"}},"next_cursor":{"type":["string","null"]}}},"InboundDelivery":{"type":"object","description":"One dispatch of this message to one channel of one recipient (webhook, forward, Telegram or Slack) — one item per (recipient, channel, kind). `status` is OVEYON's side of the story: `delivered` means your endpoint answered 2xx, the chat accepted the notice, or the forwarded copy entered OVEYON's outbound queue — NOT that a mailbox has it. For the forward channel, `destination` is what the destination MX answered when the copy reached it; read both before saying «it arrived».","properties":{"id":{"type":"string","format":"uuid"},"recipient":{"type":"object","properties":{"id":{"type":"string"},"to":{"type":"string"}}},"channel":{"type":"string","enum":["webhook","forward","telegram","slack"]},"kind":{"type":"string","enum":["received","quarantined","retro"],"description":"What was dispatched: the message itself at arrival (`received`), the quarantine notice (`quarantined`) or the retroactive link-check notice (`retro`)."},"status":{"type":"string","enum":["pending","delivered","failed","dropped","held"]},"detail":{"type":["string","null"],"description":"Human-readable outcome of the last attempt (e.g. \"HTTP 404\"). Free text — never branch on it."},"attempts":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"},"destination":{"type":["object","null"],"description":"Forward channel only, and only when the copy left through one of OVEYON's sending nodes (the usual route; a copy the master delivers directly in a fallback stays `null`): the destination MX's verdict on the forwarded copy. `null` = not a forward, or not known yet. Synchronous verdict only — a mailbox that accepts and bounces later (asynchronous DSN) is not reflected here. And `accepted` is what the MX said, not what the mailbox shows: Gmail, for one, answers 250 and then silently discards (no Spam, no Trash) a copy whose Message-ID that account has already seen — duplicate suppression — and forwarding keeps the original Message-ID on purpose (it is what preserves the conversation and the sender's DKIM).","properties":{"status":{"type":"string","enum":["accepted","deferred","bounced"]},"response":{"type":["string","null"],"description":"The raw SMTP response, e.g. \"250 2.0.0 OK … - gsmtp\" or \"550 5.1.1 … does not exist\"."},"host":{"type":["string","null"],"description":"The MX that answered."},"at":{"type":["string","null"],"format":"date-time","description":"When it answered, as stamped by the node that delivered; `null` when the node sent no usable stamp."}}}}},"InboundReply":{"type":"object","description":"A reply you sent in this thread (through the portal, Telegram, Slack or the API).","properties":{"id":{"type":"string","format":"uuid","description":"Same id as the outbound message (GET /v1/messages/{id})."},"at":{"type":"string","format":"date-time"},"origin":{"type":"string","enum":["portal","telegram","slack","api","draft"]},"from":{"type":"string"},"to":{"type":"string"},"subject":{"type":["string","null"]},"preview":{"type":["string","null"],"description":"First 500 characters of the reply text."}}},"InboundThreadDetail":{"allOf":[{"$ref":"#/components/schemas/InboundThread"},{"type":"object","properties":{"messages":{"type":"array","description":"Received messages, oldest first (up to 200).","items":{"$ref":"#/components/schemas/InboundMessage"}},"replies":{"type":"array","description":"Replies sent, oldest first (up to 200).","items":{"$ref":"#/components/schemas/InboundReply"}}}}]},"InboundRetroFlagged":{"type":"object","description":"Body of the `inbound.retro_flagged` webhook: a link we delivered as clean is now listed.","properties":{"event":{"type":"string","enum":["inbound.retro_flagged"]},"eventId":{"type":"string","format":"uuid","description":"Unique per POST (it is the delivery uid, never reused) — dedupe on it. Distinct from the eventId of the original inbound.received."},"timestamp":{"type":"string","format":"date-time"},"message":{"type":"object","properties":{"id":{"type":"string"},"subject":{"type":["string","null"]},"fromHeader":{"type":["string","null"]}},"additionalProperties":true},"recipient":{"type":"object","properties":{"id":{"type":"string"},"to":{"type":"string"}},"additionalProperties":true},"retro":{"type":"object","properties":{"reason":{"type":"string","enum":["link_listed_after_delivery"]},"message":{"type":"string"},"links":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"threatTypes":{"type":["array","null"],"items":{"type":"string"}}},"additionalProperties":true}}},"additionalProperties":true}},"additionalProperties":true},"InboundPolicyInfo":{"type":["object","null"],"description":"The inbound policy outcome for this message (F4): the effective action, the name of the policy that closed the chain (or the first one that matched), and the accumulated tags. `would_<action>:<name>` tags (e.g. `would_hold:…`, `would_restrict_channels:…`) mean a policy WOULD have held/muted/restricted it, but it did not apply — see the decision feed for why; an agent should treat them as the rule's intent. null = no policy matched.","properties":{"action":{"type":"string","enum":["hold","mute","tag","restrict_channels","pass","notify"]},"name":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}}}},"InboundPolicyCondition":{"type":"object","required":["field","op","value"],"description":"NULL never matches (a message without a verdict is neither clean nor dangerous; `listed_link` is null when the link check did not judge every link). Text comparisons are case-insensitive and ignore invisible/bidi/tag characters (the same cleaning as the agent-safety verdict); `matches` is a glob where `*` means any sequence — never a regex. `in` takes a list (up to 20). `recipient`, `attachment.name` and `attachment.type` match when ANY item matches, over the first 100 items. `body` is judged on the text AND on the HTML without tags (either matches), on the same sample the arrival judges (head + tail, 128 KiB). Addresses longer than 320 characters keep their END (the domain): only `ends_with`, `contains` and globs starting with `*` can match them. `hour`/`weekday` are read in the account timezone. Trust: `sender`, `subject` and `thread.*` are what the SENDER says; `dmarc`/`spf`/`dkim`, `agent_safety.*` and `listed_link` are our measurements.","properties":{"field":{"type":"string","enum":["sender","envelope_sender","recipient","subject","body","attachment.name","attachment.type","attachment.count","size_kb","spam_score","agent_safety.score","agent_safety.verdict","dmarc","spf","dkim","listed_link","thread.new","thread.replies","hour","weekday"]},"op":{"type":"string","enum":["equals","contains","starts_with","ends_with","matches","in","at_least","at_most"]},"value":{"description":"String, integer, boolean or a list, depending on the field and operator.","oneOf":[{"type":"string"},{"type":"integer"},{"type":"boolean"},{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"integer"}]}}]}}},"InboundPolicyInput":{"type":"object","required":["name","action","conditions"],"properties":{"name":{"type":"string","maxLength":120,"description":"Unique in the account."},"action":{"type":"string","enum":["hold","mute","tag","restrict_channels","pass","notify"],"description":"`hold` holds for a human decision (notified like the quarantine); `mute` stores without dispatching or notifying (released the same way); `tag` adds tags and CONTINUES; `notify` pings the account Telegram and CONTINUES; `restrict_channels` delivers only to the listed channels; `pass` stops the policy chain (it does not bypass the mailbox quarantine)."},"conditions":{"type":"array","minItems":1,"maxItems":5,"description":"All must match (AND).","items":{"$ref":"#/components/schemas/InboundPolicyCondition"}},"actionParams":{"type":["object","null"],"description":"`tag`: { tags: [1..5, lowercase, [a-z0-9_.:-]] }. `restrict_channels`: { channels: [{ assocId } | { type: webhook|telegram|slack|forward }] } — assocId is the mailbox↔channel link from GET /v1/inbound/routes/{id}. `notify`: { note?: string ≤ 200 }. Others: none.","additionalProperties":true},"mailboxes":{"type":["array","null"],"minItems":1,"maxItems":200,"description":"Which mailboxes the policy judges. null or absent = every mailbox of the account (the default); a list = only these — mailbox ids from GET /v1/inbound/routes (integers, or `{ id }` as the policy reads back). Each recipient of a message is judged only by the policies of ITS mailbox plus the every-mailbox ones, in the single account order: a message to two mailboxes can get a different outcome per copy. A mailbox of another account answers 400 unknown_mailbox.","items":{"oneOf":[{"type":"integer","minimum":1},{"type":"object","required":["id"],"properties":{"id":{"type":"integer","minimum":1}}}]}},"version":{"type":"integer","description":"PUT only, optional: the version you read. A different current version answers 409 inbound_policy_version_conflict instead of overwriting."}}},"InboundPolicy":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"position":{"type":"integer","description":"Evaluation order (0 = first)."},"action":{"type":"string"},"actionParams":{"type":["object","null"],"additionalProperties":true},"conditions":{"type":"array","items":{"$ref":"#/components/schemas/InboundPolicyCondition"}},"mailboxes":{"type":["array","null"],"description":"null = every mailbox of the account; a list = only these mailboxes. An EMPTY list means the chosen mailboxes were deleted: the policy judges no mailbox until you edit it.","items":{"type":"object","properties":{"id":{"type":"integer"},"address":{"type":"string","description":"`local@domain`, or `*@domain` for a catch-all."}}}},"paused":{"type":"boolean","description":"New policies are born PAUSED at the end of the order: nothing changes until you unpause."},"pausedAt":{"type":["string","null"],"format":"date-time"},"version":{"type":"integer"},"readable":{"type":"boolean","description":"false = the stored policy can no longer be interpreted and is SKIPPED at arrival until fixed."},"effectiveActionToday":{"type":["string","null"],"description":"List only. What it does TODAY: `tag` when hold/mute/restrict_channels degrade (platform switch off, or the held cap reached). null when paused."},"degradedReason":{"type":["string","null"],"enum":["hold_disabled","hold_limit",null],"description":"Why the action TODAY is a tag: the platform switch is off (hold_disabled) or the policy already holds its cap (hold_limit). null while paused or unreadable (`readable: false`) — such a policy is not evaluated."},"matches30d":{"type":"integer"},"lastMatchedAt":{"type":["string","null"],"format":"date-time"},"heldNow":{"type":"integer","description":"Messages this policy holds right now (cap 200 per policy)."},"createdBy":{"type":["string","null"]},"updatedBy":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":["string","null"],"format":"date-time"}}},"InboundPolicyList":{"type":"object","properties":{"policies":{"type":"array","items":{"$ref":"#/components/schemas/InboundPolicy"}},"total":{"type":"integer"},"max":{"type":"integer"},"holdEnabled":{"type":"boolean","description":"The platform switch. Off: hold, mute and restrict_channels become the tag `would_<action>:<name>`."}}},"InboundPolicyDecisionList":{"type":"object","properties":{"decisions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"policyId":{"type":["integer","null"],"description":"null after the policy was deleted — the frozen name stays."},"policyName":{"type":"string"},"message":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"subject":{"type":["string","null"],"description":"null unless the key also has read:inbound (the envelope is mailbox content)."},"fromHeader":{"type":["string","null"],"description":"null unless the key also has read:inbound."},"outcome":{"type":["string","null"]}}},"recipient":{"type":["string","null"],"description":"The mailbox this decision was for: one row per policy × recipient, since each recipient is judged by the policies of its own mailbox. null = a decision from before per-mailbox policies."},"action":{"type":"string"},"effectiveAction":{"type":"string"},"degradedReason":{"type":["string","null"],"enum":["hold_disabled","hold_limit","limit_unreadable","no_notice","no_channel","no_chat","notify_failed","mailbox_quarantine",null],"description":"Why the action did not apply as written. hold/mute/restrict became a tag because: the platform switch is off (hold_disabled), the policy already holds its cap (hold_limit), the held count could not be read (limit_unreadable — never holds blindly), no channel could carry the hold notice (no_notice), or the restriction would leave a recipient without channels (no_channel). notify reached no one because: the account has no active Telegram chat (no_chat), or Telegram refused after the retries (notify_failed). hold/mute stayed a hold but the mailbox's own security quarantine holds the message (mailbox_quarantine): the notice was the quarantine's, with no policy deadline and outside the policy cap. Never retained silently."},"detail":{"type":["string","null"],"description":"JSON text: the tags (tag), the channels (restrict_channels) or the note (notify)."},"decidedAt":{"type":"string","format":"date-time"}}}},"nextBefore":{"type":["integer","null"]},"retentionDays":{"type":"integer"}}},"InboundPolicySimulateRequest":{"type":"object","required":["message"],"properties":{"message":{"description":"A received message id (uuid), or a synthetic message.","oneOf":[{"type":"string","format":"uuid"},{"type":"object","properties":{"from":{"type":"string"},"envelopeFrom":{"type":"string"},"to":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"subject":{"type":"string"},"text":{"type":"string"},"html":{"type":"string"},"attachments":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"}}}},"sizeKb":{"type":"integer"},"spamScore":{"type":"integer"},"agentSafety":{"type":"object","properties":{"verdict":{"type":"string"},"score":{"type":"integer"}}},"dmarc":{"type":"string"},"spf":{"type":"string"},"dkim":{"type":"string"},"listedLink":{"type":"boolean"},"thread":{"type":"object","properties":{"new":{"type":"boolean"},"replies":{"type":"integer"}}},"receivedAt":{"type":"string","format":"date-time"}}}]},"policy":{"$ref":"#/components/schemas/InboundPolicyInput"},"policyId":{"type":"integer","description":"Evaluate this SAVED policy alone — even paused, which is how you check a policy right after creating it (new policies are born paused). Send `policy` OR `policyId`, not both."}}},"InboundPolicySimulateResult":{"type":"object","properties":{"holdEnabled":{"type":"boolean"},"recipients":{"type":"array","description":"One item per recipient: each is judged by the policies of ITS mailbox plus the every-mailbox ones. On a synthetic message the addresses are matched to the mailboxes of the account the way arrival does (an address with no mailbox is judged only by every-mailbox policies). `matched` and `result` at the top are the FIRST recipient's.","items":{"type":"object","properties":{"recipient":{"type":["string","null"]},"mailboxId":{"type":["integer","null"]},"matched":{"type":"array","items":{"type":"object","properties":{"id":{"type":["integer","null"]},"name":{"type":"string"},"action":{"type":"string"}}}},"result":{"type":"object","additionalProperties":true}}}},"matched":{"type":"array","items":{"type":"object","properties":{"id":{"type":["integer","null"]},"name":{"type":"string"},"action":{"type":"string"}}}},"result":{"type":"object","properties":{"action":{"type":["string","null"]},"policy":{"type":["string","null"]},"degradedReason":{"type":["string","null"],"description":"Same values as the decision feed. On a received message the simulator also predicts mailbox_quarantine, no_notice and no_channel from the mailboxes the message went to."},"tags":{"type":"array","items":{"type":"string"}},"notify":{"type":"array","items":{"type":"string"}},"channels":{"type":["object","null"],"additionalProperties":true}}},"seen":{"type":"object","description":"What the engine saw — so \"did not match\" tells whether it was the rule or the data.","additionalProperties":true}}},"InboundQuarantineInfo":{"type":["object","null"],"description":"Present ONLY on a message that was held by the agent-safety verdict (or, later, a reception policy) and then released by a human — panel or POST /v1/inbound/{uid}/release. Absent (null) on a normal delivery. Release is not absolution: agentSafety travels with it.","required":["reason","heldAt","releasedAt"],"properties":{"reason":{"type":"string","enum":["agent_safety_dangerous","policy"]},"heldAt":{"type":"string","format":"date-time"},"releasedAt":{"type":"string","format":"date-time"},"releasedBy":{"type":["string","null"],"description":"`portal:<user>` or `api:<key prefix>`."},"agentSafety":{"type":["object","null"],"properties":{"verdict":{"type":"string"},"score":{"type":"integer"}}},"policy":{"type":["object","null"],"description":"When one of your inbound policies held it (reason `policy`): its name and action.","properties":{"name":{"type":"string"},"action":{"type":["string","null"]}}}}},"InboundReleaseResult":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["released"]},"deliveries":{"type":"integer","description":"How many channel deliveries were queued now. 0 with noChannel=true means the mailbox has no active channel — the message stays readable in the panel and the API."},"noChannel":{"type":"boolean"},"releasedAt":{"type":"string","format":"date-time"},"releasedBy":{"type":"string"},"recipients":{"type":"array","items":{"type":"string"},"description":"The mailboxes whose copies this call released."},"stillHeld":{"type":"integer","description":"Copies of the message still held after this call (releasing one recipient of several)."}}},"InboundStatsRow":{"type":"object","properties":{"day":{"type":"string","description":"group_by=day"},"recipient":{"type":"string","description":"group_by=recipient"},"received":{"type":"integer"},"evaluated":{"type":"integer","description":"Messages with an agent-safety verdict (received minus the ones the engine could not judge)."},"clean":{"type":"integer"},"suspicious":{"type":"integer"},"dangerous":{"type":"integer"},"quarantined":{"type":"integer","description":"Held at arrival by the agent-safety verdict in the window (holds by your inbound policies are in the policy feed, not here)."},"released":{"type":"integer","description":"Verdict holds released by a human in the window."}}},"InboundStats":{"type":"object","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"groupBy":{"type":"string","enum":["day","recipient"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/InboundStatsRow"}}}},"InboundQuarantined":{"type":"object","additionalProperties":false,"description":"Body of the `inbound.quarantined` webhook: the message was HELD before delivery — by the mailbox agent-safety quarantine or by one of your inbound policies (decision: we never hold without telling you). The body is not delivered; a human releases it in the panel or via API.","properties":{"event":{"type":"string","enum":["inbound.quarantined"]},"eventId":{"type":"string","format":"uuid","description":"Unique per POST (it is the delivery uid, never reused) — dedupe on it."},"timestamp":{"type":"string","format":"date-time"},"message":{"type":"object","properties":{"id":{"type":"string"},"subject":{"type":["string","null"]},"fromHeader":{"type":["string","null"]}},"additionalProperties":true},"recipient":{"type":"object","properties":{"id":{"type":"string"},"to":{"type":"string"}},"additionalProperties":true},"quarantine":{"type":"object","required":["reason","heldAt","agentSafety","release"],"properties":{"reason":{"type":"string","enum":["agent_safety_dangerous","policy"],"description":"`agent_safety_dangerous` = the mailbox quarantine held it on the verdict; `policy` = one of your inbound policies (action hold) held it — see `policy`."},"heldAt":{"type":"string","format":"date-time"},"expiresAt":{"type":["string","null"],"format":"date-time","description":"Policy holds follow the mailbox retention: after this moment the copy is deleted and release/reply answer 410 copy_expired. null for the agent-safety quarantine, whose clock only starts after release."},"agentSafety":{"type":["object","null"],"properties":{"verdict":{"type":"string","enum":["clean","suspicious","dangerous"]},"score":{"type":"integer","minimum":0,"maximum":100}}},"policy":{"type":["object","null"],"description":"Present when reason is `policy`.","properties":{"name":{"type":"string"},"action":{"type":"string","enum":["hold"]}}},"release":{"type":"object","properties":{"panel":{"type":"string","format":"uri","description":"Where a human decides (Inbound → Deliveries, «Held»). Replying via API answers 409 message_quarantined until then."},"note":{"type":"string"}}}}}},"required":["event","eventId","timestamp","message","recipient","quarantine"]},"AgentSafetyCoverage":{"type":["object","null"],"description":"What the verdict actually looked at. null = message from before coverage was recorded. Read it before trusting `clean`.","properties":{"bodySampled":{"type":"boolean","description":"true when the scanner saw a head+tail sample (128 KiB) instead of the whole text/HTML, or a textual attachment was cut at its cap. A payload planted in the unsampled middle is not seen. Also true when an absurd header (a field over 64 KiB, a header block over 512 KiB, or over 5000 lines — 1000 in a MIME part) was cut before reading, or the message has more than 1000 MIME parts (the first 1000 are read)."},"sanitized":{"type":"boolean","description":"true when hidden characters (Unicode Tags, zero-width, bidi overrides) were stripped from the text we hand you — the body, the subject or the sender display name (`fromName`). The raw copy (/raw) keeps them, for forensics."},"unscannedAttachments":{"type":"integer","minimum":0,"description":"Attachments whose content the scanner did not read because they are not text: PDFs, images, spreadsheets, archives. Extract them yourself before a model reads them. Attached messages (.eml) ARE decoded and scanned."}},"required":["bodySampled","sanitized","unscannedAttachments"]},"AgentSafety":{"type":["object","null"],"properties":{"verdict":{"type":"string","enum":["clean","suspicious","dangerous"],"description":"clean | suspicious | dangerous. Treat `null` (field absent) as «not evaluated», never as safe."},"score":{"type":"integer","minimum":0,"maximum":100,"description":"0 = nothing found, 100 = maximum risk. The thresholds are 25 (suspicious) and 60 (dangerous)."},"coverage":{"$ref":"#/components/schemas/AgentSafetyCoverage"},"signals":{"type":"array","description":"Why the score is what it is. Present in the webhook payload and in GET /v1/inbound/{uid}; omitted from the paginated list. Each entry names one finding.","items":{"type":"object","properties":{"type":{"type":"string","description":"Machine name of the finding, e.g. unicode_tags, imperativo_oculto, bidi, homoglifo."},"points":{"type":"integer","description":"How much this finding added to the score."},"detail":{"type":"string","description":"Human-readable evidence — including the hidden text, decoded, so you can see what your agent would have read."}},"additionalProperties":true}}},"additionalProperties":true},"InboundList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InboundMessage"}},"next_cursor":{"type":["string","null"]}}},"InboundRouteChannel":{"type":"object","description":"A notification channel attached to this mailbox. Never carries the webhook secret.","properties":{"assocId":{"type":"integer","description":"The attachment id — use it in DELETE /v1/inbound/routes/{id}/channels/{assocId}."},"channelId":{"type":"integer"},"type":{"type":"string","enum":["webhook","telegram","slack","forward"]},"label":{"type":["string","null"]},"destination":{"type":["string","null"],"description":"Webhook URL, forward address, Telegram chat or Slack channel."},"mode":{"type":["string","null"],"enum":["full+attachments","full","summary",null]},"active":{"type":"boolean"},"broken":{"type":"boolean","description":"The channel is marked broken (deliveries failing) — see the panel."}}},"InboundRoute":{"type":"object","properties":{"id":{"type":"integer"},"domain":{"type":"string"},"address":{"type":"string","description":"`local@domain`, or `*@domain` for a catch-all."},"matchType":{"type":"string","enum":["exact","catchall"]},"localPart":{"type":["string","null"]},"active":{"type":"boolean"},"inboundEnabled":{"type":"boolean","description":"Whether inbound is switched on for the DOMAIN (portal). A mailbox on a domain with inbound off does not receive yet."},"agentSafetyMode":{"type":["string","null"],"description":"null = the mailbox delivers everything with the verdict (default for API-created mailboxes); `dangerous` = holds dangerous messages (set in the portal)."},"retroScan":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":["string","null"],"format":"date-time"},"createdBy":{"type":["string","null"],"description":"`api:<key prefix>`, `portal:<user>`, `wizard:<user>`, `admin:<operator>`; null = before this was recorded."},"channelCount":{"type":"integer","description":"List only: active channels attached."},"channels":{"type":"array","description":"Detail only.","items":{"$ref":"#/components/schemas/InboundRouteChannel"}},"url":{"type":"string"}}},"InboundRouteCreate":{"type":"object","required":["domain","matchType"],"properties":{"domain":{"type":"string","description":"A domain of your account (name, not id)."},"matchType":{"type":"string","enum":["exact","catchall"]},"localPart":{"type":"string","maxLength":64,"description":"Required for `exact` (the part before the @). Lower-cased."},"active":{"type":"boolean","default":true},"channels":{"type":"array","maxItems":5,"description":"Attach channels in the same call: `{ channelId, mode? }` for an existing channel of the same domain, or `{ type: \"webhook\", url, mode? }` for a NEW webhook (at most one per call; the endpoint must echo the ownership challenge — see the docs). The webhook secret is returned once, in the 201.","items":{"type":"object","properties":{"channelId":{"type":"integer"},"type":{"type":"string","enum":["webhook"]},"url":{"type":"string","format":"uri"},"mode":{"type":"string","enum":["full+attachments","full","summary"]}}}}}},"InboundRoutePatch":{"type":"object","description":"Additive: switches `active` and ATTACHES channels. Removing a channel is an explicit DELETE on its assocId.","properties":{"active":{"type":"boolean"},"channels":{"type":"array","maxItems":5,"items":{"type":"object","properties":{"channelId":{"type":"integer"},"type":{"type":"string","enum":["webhook"]},"url":{"type":"string","format":"uri"},"mode":{"type":"string"}}}}}},"InboundRouteCreated":{"allOf":[{"$ref":"#/components/schemas/InboundRoute"},{"type":"object","properties":{"webhook":{"type":"object","description":"Only when a webhook was created inline. The secret is shown ONCE.","properties":{"url":{"type":"string"},"mode":{"type":"string"},"secret":{"type":"string"},"signature":{"$ref":"#/components/schemas/SignatureDoc"}}},"channelErrors":{"type":"array","description":"Channels that could not be attached (the mailbox was still created).","items":{"type":"object","properties":{"type":{"type":"string"},"error":{"type":"string"}}}}}}]},"InboundRouteList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InboundRoute"}}}},"InboundReplyRequest":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":20000,"description":"Plain-text body of the reply. The From is always the mailbox that received the message; the To is the original Reply-To/From; In-Reply-To and References are set for you."},"subject":{"type":"string","maxLength":500,"description":"Optional. Defaults to `Re: <original subject>`. Line breaks are rejected (400 bad_subject)."},"idempotencyKey":{"type":"string","description":"Optional (or the Idempotency-Key header). The same key returns the same reply id instead of sending twice. Namespaced apart from /v1/send keys."},"from":{"type":"string","maxLength":320,"description":"Optional: which mailbox of the message replies (one of `recipients[].to`). Default: the first mailbox whose copy was delivered. A mailbox whose copy is held answers 409 recipient_quarantined; an address that did not receive the message, 422 from_not_recipient."},"hold":{"type":"boolean","description":"Hold the REPLY for a human to approve (status `held`); the key that replies does not approve (approve:holds)."},"holdTtl":{"type":"integer","minimum":300,"maximum":604800,"description":"Seconds until the held reply expires (default 86400)."},"holdNote":{"type":"string","maxLength":500,"description":"What the agent wants the approver to know."}}},"InboundReplyAccepted":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"The outbound message id — follow it in GET /v1/messages/{id} and the delivery webhooks."},"status":{"type":"string","enum":["accepted","held"],"description":"`held` = sent with hold:true — waiting for a human (see `hold`). A repeated idempotencyKey answers 200 with DuplicateResponse instead."},"hold":{"type":"object","description":"Only when status is `held`.","properties":{"expiresAt":{"type":"string","format":"date-time"},"note":{"type":["string","null"]},"requestedBy":{"type":"string"},"approve":{"type":"string"},"reject":{"type":"string"}}},"from":{"type":"string"},"to":{"type":"string"},"subject":{"type":"string"},"thread":{"$ref":"#/components/schemas/InboundThreadRef"},"url":{"type":"string"}}},"InboundDetail":{"allOf":[{"$ref":"#/components/schemas/InboundMessage"},{"type":"object","properties":{"deliveries":{"type":"array","description":"Where this message was dispatched and what happened — one item per (recipient, channel, kind), oldest first. Includes the destination MX's answer for forwarded copies.","items":{"$ref":"#/components/schemas/InboundDelivery"}},"replies":{"type":"array","description":"Replies sent for this message (portal, Telegram, Slack or API), oldest first.","items":{"$ref":"#/components/schemas/InboundReply"}},"attachments":{"type":"array","items":{"type":"object","properties":{"ord":{"type":"integer"},"filename":{"type":"string"},"contentType":{"type":"string"},"sizeBytes":{"type":"integer"},"sha256":{"type":"string"},"url":{"type":"string"}}}},"links":{"type":"object","properties":{"self":{"type":"string"},"content":{"type":"string"},"raw":{"type":"string"},"reply":{"type":"string","description":"POST here to reply (scope reply:inbound)."}}}}}]},"InboundContent":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":["string","null"]},"html":{"type":["string","null"],"description":"Third-party HTML — sanitize before rendering."},"truncated":{"type":"boolean"},"sanitized":{"type":"boolean","description":"True when invisible smuggling characters (Unicode Tags block, zero-width, bidi overrides) were stripped from text/html, the subject or the sender display name. Use /raw for the byte-exact original."},"replyText":{"type":["string","null"],"description":"The NEW text of the message with quoted history, Outlook/forwarded headers and the RFC 3676 signature removed (pt/en). Feed this to a model, not `text`. Equals `text` when nothing was quoted."},"replyStripped":{"type":"boolean","description":"True when something was removed to produce replyText."},"replyMarkers":{"type":"array","items":{"type":"string","enum":["quote_header","quoted_lines","outlook_header","original_message","forwarded","signature"]},"description":"What was recognized and removed."}}},"Suppression":{"type":"object","properties":{"email":{"type":"string"},"reason":{"type":"string","enum":["hard_bounce","complaint","manual","unsubscribe"]},"source":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}},"additionalProperties":true},"SuppressionList":{"type":"object","properties":{"suppressions":{"type":"array","items":{"$ref":"#/components/schemas/Suppression"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}},"ListEntry":{"type":"object","properties":{"id":{"type":"integer"},"scope":{"type":"string","enum":["outbound","inbound"],"description":"Which direction the list judges."},"kind":{"type":"string","enum":["allow","block"]},"pattern":{"type":"string"},"domainId":{"type":["integer","null"],"description":"0 = every domain of the account."},"credentialType":{"type":["string","null"],"enum":["none","smtp","apiKey",null],"description":"`none` = every credential."},"credentialId":{"type":["integer","null"],"description":"0 with credentialType `none`."},"paused":{"type":"boolean"},"pausedAt":{"type":["string","null"]}},"additionalProperties":true},"IpRule":{"type":"object","properties":{"id":{"type":"integer"},"credentialType":{"type":"string","enum":["smtp","apiKey"]},"credentialId":{"type":"integer"},"cidr":{"type":"string"},"paused":{"type":"boolean"},"pausedAt":{"type":["string","null"]}},"additionalProperties":true},"Condition":{"type":"object","required":["campo","op","valor"],"properties":{"campo":{"type":"string"},"op":{"type":"string"},"valor":{"type":"string"}}},"SendPolicy":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string","maxLength":120},"conditions":{"type":"array","minItems":1,"maxItems":5,"items":{"$ref":"#/components/schemas/Condition"}},"action":{"type":"string","enum":["recusar","reter","reter_para_aprovacao","exigir_footer","forcar_transacional","seguir_fluxo","limitar_hora"],"description":"`reter` holds for the OPERATOR; `reter_para_aprovacao` (F3b) turns the message into a draft awaiting YOUR approval (GET /v1/holds)."},"actionParams":{"type":["object","null"],"additionalProperties":true},"position":{"type":"integer"},"paused":{"type":"boolean"},"pausedAt":{"type":["string","null"]}},"additionalProperties":true},"SendPolicyInput":{"type":"object","required":["name","conditions","action"],"properties":{"name":{"type":"string","maxLength":120},"conditions":{"type":"array","minItems":1,"maxItems":5,"items":{"$ref":"#/components/schemas/Condition"}},"action":{"type":"string"},"actionParams":{"type":"object","additionalProperties":true}}},"TemplateVersionInput":{"type":"object","required":["subject"],"description":"The CONTENT of a new version. The name is the template identity and does not change here (a `name` in the body is ignored).","properties":{"subject":{"type":"string"},"html":{"type":"string"},"text":{"type":"string"}}},"TemplateVar":{"type":"object","additionalProperties":false,"required":["name","kind","required","default"],"description":"A variable the template reads.","properties":{"name":{"type":"string","description":"The ROOT of the path the template reads — the key to send in `data`: `{{pedido.total}}` and `{{#if pedido.pago}}` both declare `pedido` (send it as an object). Paths inside `{{#each}}` are relative to the item and are not listed."},"kind":{"type":"string","enum":["var","flag","list"],"description":"`var` = printed; `flag` = used in a condition; `list` = iterated by `{{#each}}`."},"required":{"type":"boolean","description":"true = a printed variable with no `| \"default\"`."},"default":{"type":["string","null"]}}},"TemplateDraft":{"type":"object","additionalProperties":false,"required":["id","name","version","status","vars"],"description":"The draft just saved (create / new version).","properties":{"id":{"type":"integer"},"name":{"type":"string"},"version":{"type":"integer"},"status":{"type":"string","enum":["draft"]},"vars":{"type":"array","items":{"$ref":"#/components/schemas/TemplateVar"}}}},"TemplatePublished":{"type":"object","additionalProperties":false,"required":["id","name","version","status"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"version":{"type":"integer","description":"The version that is now current."},"status":{"type":"string","enum":["published"]}}},"TemplateVersion":{"type":"object","additionalProperties":false,"required":["version","status","subject","html","text","vars","createdBy","createdAt"],"properties":{"version":{"type":"integer"},"status":{"type":"string","enum":["draft","published"],"description":"`published` is immutable."},"subject":{"type":"string"},"html":{"type":["string","null"]},"text":{"type":["string","null"]},"vars":{"type":"array","items":{"$ref":"#/components/schemas/TemplateVar"}},"createdBy":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}}},"Template":{"type":"object","additionalProperties":false,"required":["id","name","currentVersion","createdAt","versions"],"description":"A template with all its versions.","properties":{"id":{"type":"integer"},"name":{"type":"string"},"currentVersion":{"type":["integer","null"],"description":"The version sends use; null = never published."},"createdAt":{"type":"string","format":"date-time"},"versions":{"type":"array","items":{"$ref":"#/components/schemas/TemplateVersion"}}}},"TemplateList":{"type":"object","additionalProperties":false,"required":["templates"],"properties":{"templates":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["id","name","currentVersion","latestVersion","drafts","createdAt"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"currentVersion":{"type":["integer","null"]},"latestVersion":{"type":"integer"},"drafts":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}}}}},"TemplateDeleted":{"type":"object","additionalProperties":false,"required":["ok","id"],"description":"The template and all its versions are gone.","properties":{"ok":{"type":"boolean","enum":[true]},"id":{"type":"integer","description":"The id of the deleted template."}}},"TemplateInput":{"type":"object","required":["name","subject"],"properties":{"name":{"type":"string","maxLength":120,"description":"Unique in the account; starts with a letter; letters, digits, . - _"},"subject":{"type":"string"},"html":{"type":"string"},"text":{"type":"string"}}},"Survey":{"type":"object","properties":{"id":{"type":"integer"},"kind":{"type":"string","enum":["nps","csat"]},"name":{"type":"string"},"subject":{"type":"string"},"fromEmail":{"type":"string"},"fromName":{"type":["string","null"]},"question":{"type":"string"},"throttleDays":{"type":"integer","minimum":7,"maximum":3650},"brandColor":{"type":["string","null"]},"logoUrl":{"type":["string","null"]},"active":{"type":"boolean"},"rollup":{"$ref":"#/components/schemas/SurveyRollup"}},"additionalProperties":true},"SurveyInput":{"type":"object","required":["kind","name","subject","fromEmail"],"properties":{"kind":{"type":"string","enum":["nps","csat"],"description":"Cannot change after creation."},"name":{"type":"string"},"subject":{"type":"string"},"fromEmail":{"type":"string","description":"A domain of your account."},"fromName":{"type":"string"},"question":{"type":"string","description":"Optional. Empty = the canonical wording of the kind, in the account language (the language of the platform emails you receive: Portuguese for an account from Brazil, English for all others). Once saved it is your text; nothing rewrites it."},"throttleDays":{"type":"integer","minimum":7,"maximum":3650,"default":90},"brandColor":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"logoUrl":{"type":"string","format":"uri"},"active":{"type":"boolean"}}},"SurveyResponse":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"integer"},"sendId":{"type":"integer"},"recipient":{"type":"string"},"score":{"type":"integer"},"comment":{"type":["string","null"]},"updated":{"type":"boolean","description":"true = the person changed the score later, with confirmation."},"meta":{"type":["object","null"],"additionalProperties":true,"description":"The context you sent with the survey."},"messageId":{"type":["string","null"],"description":"uuid of the message that carried the survey."},"respondedAt":{"type":"string","format":"date-time"},"updatedAt":{"type":["string","null"],"format":"date-time"}}},"SurveyRollup":{"type":["object","null"],"additionalProperties":false,"description":"NPS/CSAT rollup — only on GET /v1/surveys/{id} and with the responses; null when the survey is gone.","properties":{"surveyId":{"type":"integer"},"kind":{"type":"string","enum":["nps","csat"]},"enviados":{"type":"integer","description":"Surveys sent (not canceled)."},"respostas":{"type":"integer"},"taxaResposta":{"type":["number","null"],"description":"null when nothing was sent."},"nps":{"type":["number","null"],"description":"NPS only; null without responses — never 0."},"promotores":{"type":["integer","null"]},"detratores":{"type":["integer","null"]},"neutros":{"type":["integer","null"]},"media":{"type":["number","null"]},"distribuicao":{"type":"object","additionalProperties":{"type":"integer"},"description":"Every score of the range, zeros included."}}},"Webhook":{"type":"object","properties":{"id":{"type":"integer"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["delivered","bounced","opened","clicked","complained","blocked"]}},"active":{"type":"boolean"},"credential":{"type":["object","null"],"properties":{"type":{"type":"string","enum":["smtp","api"]},"id":{"type":"integer"}}}},"additionalProperties":true},"WebhookCreated":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"secret":{"type":"string","description":"Shown ONCE. Signs x-oveyon-signature."},"signature":{"$ref":"#/components/schemas/SignatureDoc"}}}]},"Domain":{"type":"object","properties":{"id":{"type":"integer"},"domain":{"type":"string"},"records":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"name":{"type":"string"},"value":{"type":"string"},"purpose":{"type":"string"},"optional":{"type":"boolean","description":"true = not needed to send (e.g. the tracking CNAME)."}}}},"verification":{"type":"object","properties":{"spf":{"type":"boolean"},"dkim":{"type":"boolean"},"dmarc":{"type":"boolean"},"inconclusive":{"type":"array","items":{"type":"string"}}},"additionalProperties":true}},"additionalProperties":true},"Decision":{"type":"object","additionalProperties":true,"description":"One send-policy decision (newest first)."}}},"security":[{"bearer":[]}]}