{"openapi":"3.1.0","info":{"title":"Posthaste API","version":"1.0.0","description":"Transactional email from our own MTA: mail is signed and delivered directly to recipient mail servers.\n\nAcceptance is not delivery. `POST /v1/emails` answers 202 once the message is durably stored and queued; the outcome arrives on a signed webhook or in the message log."},"servers":[{"url":"https://api.posthastemail.dev","description":"Production"}],"tags":[{"name":"API keys"},{"name":"Account"},{"name":"Alerts"},{"name":"Audit"},{"name":"Auth"},{"name":"Billing"},{"name":"Deliverability"},{"name":"Domains"},{"name":"Emails"},{"name":"Inbound"},{"name":"Messages"},{"name":"Other"},{"name":"Streams"},{"name":"Sub-accounts"},{"name":"Suppressions"},{"name":"Team"},{"name":"Templates"},{"name":"Webhooks"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"A Posthaste API key. The environment is part of the token prefix, so a test key presented as a live one does not authenticate."},"session":{"type":"apiKey","in":"cookie","name":"ph_session","description":"The dashboard session cookie (httpOnly, Secure, SameSite=Lax). Mutations additionally require the CSRF token to be echoed in X-CSRF-Token."}}},"paths":{"/v1/auth/signup":{"post":{"operationId":"postAuthSignup","summary":"Create an account","description":"No authentication: creates the first account.","tags":["Auth"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","maxLength":320},"password":{"type":"string","maxLength":256},"accountName":{"type":"string","minLength":1,"maxLength":120}},"required":["email","password"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/login":{"post":{"operationId":"postAuthLogin","summary":"Sign in","description":"No authentication: is how a session is obtained.","tags":["Auth"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","maxLength":320},"password":{"type":"string","maxLength":256},"code":{"type":"string","maxLength":32}},"required":["email","password"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/logout":{"post":{"operationId":"postAuthLogout","summary":"Sign out","description":"No authentication: must work even with a dead session.","tags":["Auth"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/openapi.json":{"get":{"operationId":"getOpenapi.json","summary":"This document","description":"Served from the running server, so it always describes the API you are calling.\n\nNo authentication: describes only the public API, which is equally discoverable by trying it.","tags":["Other"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/me":{"get":{"operationId":"getAuthMe","summary":"Who am I","description":"How the dashboard learns its auth state on boot; no token is readable from JavaScript.\n\nNo authentication: answers \"am I signed in\" for the dashboard on boot.","tags":["Auth"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/google/start":{"get":{"operationId":"getAuthGoogleStart","summary":"Begin Google sign-in","description":"No authentication: is how a session is obtained.","tags":["Auth"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/google/callback":{"get":{"operationId":"getAuthGoogleCallback","summary":"Google sign-in callback","description":"No authentication: is how a session is obtained.","tags":["Auth"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/google/nonce":{"get":{"operationId":"getAuthGoogleNonce","summary":"Nonce for Google One Tap","description":"No authentication: is how a session is obtained.","tags":["Auth"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/google/one-tap":{"post":{"operationId":"postAuthGoogleOneTap","summary":"Sign in with a Google One Tap credential","description":"No authentication: is how a session is obtained.","tags":["Auth"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/passkeys/login/options":{"post":{"operationId":"postAuthPasskeysLoginOptions","summary":"Begin passkey sign-in","description":"No authentication: is how a session is obtained.","tags":["Auth"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/passkeys/login/verify":{"post":{"operationId":"postAuthPasskeysLoginVerify","summary":"Complete passkey sign-in","description":"No authentication: is how a session is obtained.","tags":["Auth"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/password/forgot":{"post":{"operationId":"postAuthPasswordForgot","summary":"Request a password reset","description":"Answers the same way whether or not the address is known, so it cannot be used to test which addresses exist.\n\nNo authentication: is reached by somebody who has by definition failed to sign in.","tags":["Auth"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/password/reset":{"post":{"operationId":"postAuthPasswordReset","summary":"Set a new password with a reset token","description":"No header credential. A single-use token in the request body IS the credential, and it is reached from a mailbox.","tags":["Auth"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/verify-email":{"post":{"operationId":"postAuthVerifyEmail","summary":"Verify an email address","description":"No header credential. A single-use token in the request body IS the credential, and it is reached from a mailbox.","tags":["Auth"],"security":[],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/team/invites/accept":{"post":{"operationId":"postTeamInvitesAccept","summary":"Accept an invitation","description":"Sets the password and verifies the address. Does not sign you in.\n\nNo header credential. A single-use token in the request body IS the credential, and it is reached from a mailbox.","tags":["Team"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","maxLength":200},"password":{"type":"string","maxLength":256}},"required":["token","password"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/billing/{provider}/webhook":{"post":{"operationId":"postBillingByProviderWebhook","summary":"Payment provider webhook","description":"Authenticated by the provider signature over the RAW body, so it cannot be re-signed from a parsed copy.\n\nNo header credential. The caller is a payment or mail provider and is authenticated by its signature over the RAW request body — so this operation cannot be re-signed from a parsed copy.","tags":["Billing"],"security":[],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/leads":{"post":{"operationId":"postLeads","summary":"Contact-form enquiry","description":"Requires the platform account's own key: the marketing contact form.","tags":["Other"],"security":[{"apiKey":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/profile":{"patch":{"operationId":"patchProfile","summary":"Change your own name","description":"Requires a signed-in session, acting on your own record — set your own name. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Account"],"security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":120}},"required":["name"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/password/change":{"post":{"operationId":"postAuthPasswordChange","summary":"Change your password","description":"Requires a signed-in session, acting on your own record — change your own password. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Auth"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/verify-email/send":{"post":{"operationId":"postAuthVerifyEmailSend","summary":"Resend the verification email","description":"Requires a signed-in session, acting on your own record — resend your own confirmation. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Auth"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/sessions":{"get":{"operationId":"getAuthSessions","summary":"List your active sessions","description":"Requires a signed-in session, acting on your own record — list your own sessions. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Auth"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/sessions/revoke-others":{"post":{"operationId":"postAuthSessionsRevokeOthers","summary":"Sign out everywhere else","description":"Requires a signed-in session, acting on your own record — sign your other devices out. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Auth"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/2fa/setup":{"post":{"operationId":"postAuth2faSetup","summary":"Begin TOTP enrolment","description":"Requires a signed-in session, acting on your own record — enrol your own authenticator. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Auth"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/2fa/enable":{"post":{"operationId":"postAuth2faEnable","summary":"Enable TOTP","description":"Requires a signed-in session, acting on your own record — enrol your own authenticator. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Auth"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/2fa/disable":{"post":{"operationId":"postAuth2faDisable","summary":"Disable TOTP","description":"Requires a signed-in session, acting on your own record — remove your own authenticator. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Auth"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/passkeys":{"get":{"operationId":"getAuthPasskeys","summary":"List your registered passkeys","description":"Requires a signed-in session, acting on your own record — list your own passkeys. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Auth"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/passkeys/register/options":{"post":{"operationId":"postAuthPasskeysRegisterOptions","summary":"Begin registering a passkey","description":"Requires a signed-in session, acting on your own record — add your own passkey. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Auth"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/passkeys/register/verify":{"post":{"operationId":"postAuthPasskeysRegisterVerify","summary":"Finish registering a passkey","description":"Requires a signed-in session, acting on your own record — add your own passkey. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Auth"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/auth/passkeys/{id}":{"delete":{"operationId":"deleteAuthPasskeysById","summary":"Remove a passkey","description":"Requires a signed-in session, acting on your own record — remove your own passkey. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Auth"],"security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/emails":{"post":{"operationId":"postEmails","summary":"Send an email","description":"Accepts a message, stores it durably, and queues it for delivery. Returns 202 with the message id — acceptance is not delivery, and the delivery outcome arrives on the webhook or in the message log.\n\nSupply `html`/`text`, or a `template` with `variables`. A missing required template variable is a refusal, not a blank: rendering fails and nothing is sent.\n\nRequires an API key with `emails:send`, or a signed-in session.","tags":["Emails"],"security":[{"apiKey":["emails:send"]},{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","minLength":3,"maxLength":320},"to":{"anyOf":[{"type":"string","minLength":3,"maxLength":320},{"minItems":1,"type":"array","items":{"type":"string","minLength":3,"maxLength":320}}]},"cc":{"type":"array","items":{"type":"string","minLength":3,"maxLength":320}},"bcc":{"type":"array","items":{"type":"string","minLength":3,"maxLength":320}},"subject":{"type":"string","maxLength":998},"text":{"type":"string","maxLength":5000000},"html":{"type":"string","maxLength":5000000},"replyTo":{"type":"string","maxLength":320},"headers":{"type":"object","propertyNames":{"type":"string","maxLength":200},"additionalProperties":{"type":"string","maxLength":2000}},"listUnsubscribe":{"type":"string","maxLength":1000},"stream":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},"template":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},"templateVersion":{"type":"integer","minimum":1,"maximum":9007199254740991},"variables":{"type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":64},"additionalProperties":{"type":"string","maxLength":5000}},"tags":{"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"metadata":{"type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":64},"additionalProperties":{"type":"string","maxLength":512}},"idempotencyKey":{"type":"string","minLength":1,"maxLength":255},"scheduledAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"attachments":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255},"contentType":{"type":"string","minLength":3,"maxLength":200},"content":{"type":"string","maxLength":13981016,"format":"base64","contentEncoding":"base64","pattern":"^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$"},"disposition":{"type":"string","enum":["attachment","inline"]},"cid":{"type":"string","maxLength":200}},"required":["filename","contentType","content"]}}},"required":["from","to"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/emails/batch":{"post":{"operationId":"postEmailsBatch","summary":"Send up to 100 emails in one call","description":"Partial success is the contract: each item succeeds or fails on its own and one bad recipient never fails the batch. Each item commits independently, so an accepted message stays accepted whatever happens to the rest.\n\nRequires an API key with `emails:send`, or a signed-in session.","tags":["Emails"],"security":[{"apiKey":["emails:send"]},{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"object","properties":{"from":{"type":"string","minLength":3,"maxLength":320},"to":{"anyOf":[{"type":"string","minLength":3,"maxLength":320},{"minItems":1,"type":"array","items":{"type":"string","minLength":3,"maxLength":320}}]},"cc":{"type":"array","items":{"type":"string","minLength":3,"maxLength":320}},"bcc":{"type":"array","items":{"type":"string","minLength":3,"maxLength":320}},"subject":{"type":"string","maxLength":998},"text":{"type":"string","maxLength":5000000},"html":{"type":"string","maxLength":5000000},"replyTo":{"type":"string","maxLength":320},"headers":{"type":"object","propertyNames":{"type":"string","maxLength":200},"additionalProperties":{"type":"string","maxLength":2000}},"listUnsubscribe":{"type":"string","maxLength":1000},"stream":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},"template":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},"templateVersion":{"type":"integer","minimum":1,"maximum":9007199254740991},"variables":{"type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":64},"additionalProperties":{"type":"string","maxLength":5000}},"tags":{"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"metadata":{"type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":64},"additionalProperties":{"type":"string","maxLength":512}},"idempotencyKey":{"type":"string","minLength":1,"maxLength":255},"scheduledAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"attachments":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255},"contentType":{"type":"string","minLength":3,"maxLength":200},"content":{"type":"string","maxLength":13981016,"format":"base64","contentEncoding":"base64","pattern":"^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$"},"disposition":{"type":"string","enum":["attachment","inline"]},"cid":{"type":"string","maxLength":200}},"required":["filename","contentType","content"]}}},"required":["from","to"]}}},"required":["messages"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/emails/{id}/schedule":{"delete":{"operationId":"deleteEmailsByIdSchedule","summary":"Cancel a scheduled message","description":"Requires an API key with `emails:send`, or a signed-in session.","tags":["Emails"],"security":[{"apiKey":["emails:send"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/messages":{"get":{"operationId":"getMessages","summary":"List messages","description":"Filterable by status, tag, stream, recipient domain, metadata and date. Repeated `tag` parameters are ANDed.\n\nRequires an API key with `messages:read`, or a signed-in session.","tags":["Messages"],"security":[{"apiKey":["messages:read"]},{"session":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"before","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["queued","sending","delivered","bounced","complained","failed","rejected","scheduled","canceled"]}},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"search","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"domain","in":"query","required":false,"schema":{"type":"string"}},{"name":"stream","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"}},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}},{"name":"metadata","in":"query","required":false,"schema":{"type":"string","maxLength":577,"pattern":"^[^:]+:.*$"}},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},{"name":"until","in":"query","required":false,"schema":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/messages/tail":{"get":{"operationId":"getMessagesTail","summary":"Stream message events as they happen","description":"A Server-Sent Events stream of the same events the message log holds, delivered as they are written. Nothing polls: the connection is idle until an event lands.\n\nEach `message` event carries the account `seq` as its SSE id. Reconnect with `Last-Event-ID` (browsers send it automatically) or `?after=<seq>` and everything after that position is replayed, so a dropped connection loses nothing.\n\nA `ready` event opens the stream, `heartbeat` arrives every 15 seconds, and `bye` says why we closed. The stream is closed after 15 minutes with no message event; reconnect and resume. Five concurrent tails per account — a sixth is refused with `too_many_streams`.\n\nRequires an API key with `messages:read`, or a signed-in session.","tags":["Messages"],"security":[{"apiKey":["messages:read"]},{"session":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["queued","sending","delivered","bounced","complained","failed","rejected","scheduled","canceled"]}},{"name":"stream","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"}},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}},{"name":"replay","in":"query","required":false,"schema":{"default":20,"type":"integer","minimum":0,"maximum":100}},{"name":"after","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":9007199254740991}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/messages/{id}":{"get":{"operationId":"getMessagesById","summary":"Fetch one message","description":"Requires an API key with `messages:read`, or a signed-in session.","tags":["Messages"],"security":[{"apiKey":["messages:read"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/messages/{id}/attachments/{attachmentId}":{"get":{"operationId":"getMessagesByIdAttachmentsByAttachmentId","summary":"Download an attachment","description":"Served `Content-Disposition: attachment` — never rendered inline on our origin, whatever the declared content type.\n\nRequires an API key with `messages:read`, or a signed-in session.","tags":["Messages"],"security":[{"apiKey":["messages:read"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"attachmentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/stats/messages":{"get":{"operationId":"getStatsMessages","summary":"Message counts over time","description":"Requires an API key with `messages:read`, or a signed-in session.","tags":["Messages"],"security":[{"apiKey":["messages:read"]},{"session":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"default":30,"type":"integer","minimum":1,"maximum":365}},{"name":"domain","in":"query","required":false,"schema":{"type":"string"}},{"name":"tz","in":"query","required":false,"schema":{"default":"UTC","type":"string","maxLength":64,"pattern":"^[A-Za-z0-9_+\\-/]+$"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/analytics/delivery":{"get":{"operationId":"getAnalyticsDelivery","summary":"Delivery rate and time to inbox, by receiving domain or by tag","description":"Answers \"is Outlook slower than Gmail for our mail\". Returns the delivery rate and the p50/p95 time to inbox for each receiving domain, or for each of your own tags, over a window you choose.\n\nTime to inbox is measured between two events on the message: `accepted`, when it entered the delivery queue, and `delivered`, when the receiving mail server accepted it. It does not include what that server does afterwards, which no sender can observe.\n\nA rate with nothing settled behind it and a percentile with no delivered message behind it are both `null`, never `0`. Every row carries the number of messages its percentiles were computed from.\n\nRequires an API key with `analytics:read`, or a signed-in session.","tags":["Deliverability"],"security":[{"apiKey":["analytics:read"]},{"session":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"default":7,"type":"integer","minimum":1,"maximum":90}},{"name":"groupBy","in":"query","required":false,"schema":{"default":"domain","type":"string","enum":["domain","tag"]}},{"name":"stream","in":"query","required":false,"schema":{"type":"string","maxLength":64}},{"name":"tag","in":"query","required":false,"schema":{"type":"string","maxLength":64}},{"name":"limit","in":"query","required":false,"schema":{"default":20,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":200}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/analytics/dmarc":{"get":{"operationId":"getAnalyticsDmarc","summary":"Who is sending as your domains, and whether it aligns","description":"Summarises the DMARC aggregate reports receiving providers send back about your domains, grouped by the IP that sent the mail and ranked by volume.\n\n`messages` is the volume the reporters accounted for, summed from each report row's own count — not a number of rows, since a single row can stand for millions of messages. A message counts as aligned when SPF **or** DKIM aligned with the From domain, which is what DMARC itself requires; a mechanism that passed for some unrelated domain does not count.\n\nA source with volume and no alignment is either a legitimate sender you have forgotten about or somebody spoofing you. The two are indistinguishable from here, so this reports what the receivers observed and leaves that judgement to you.\n\n`alignmentRate` is `null` rather than `0` when no reports have arrived yet, because \"nothing reported\" and \"everything failed\" are opposite situations.\n\nRequires an API key with `analytics:read`, or a signed-in session.","tags":["Deliverability"],"security":[{"apiKey":["analytics:read"]},{"session":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"default":30,"type":"integer","minimum":1,"maximum":90}},{"name":"domain","in":"query","required":false,"schema":{"type":"string","maxLength":253}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/audit/events":{"get":{"operationId":"getAuditEvents","summary":"Page the account audit log","description":"The append-only, hash-chained record of everything that has happened to this account’s mail, newest first. Each entry carries its own `hash` and `prevHash`, so a single row can be checked without downloading the whole log.\n\nDeliberately does NOT verify the chain: verification replays the entire history, and doing that per page would make paging cost more the longer you have been a customer. Use `/v1/audit/verify` for the verdict.\n\nRequires an API key with `messages:read`, or a signed-in session.","tags":["Audit"],"security":[{"apiKey":["messages:read"]},{"session":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":100}},{"name":"before","in":"query","required":false,"schema":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["accepted","queued","attempted","delivered","deferred","bounced","complained","failed","rejected","suppressed","scheduled","schedule_canceled","schedule_failed"]}},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},{"name":"until","in":"query","required":false,"schema":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/audit/verify":{"get":{"operationId":"getAuditVerify","summary":"Prove the audit chain is intact","description":"Replays every event on the account, in order, and reports the first break by sequence number. Answers 200 whether or not the record holds — a broken chain is evidence, not a server fault, and returning 5xx would make it look like a transient error worth retrying.\n\n`purgedThroughSeq` is non-zero when the oldest events have aged out of your plan’s retention window through the audited purge path. That is why a chain may legitimately start above 1.\n\nRequires an API key with `messages:read`, or a signed-in session.","tags":["Audit"],"security":[{"apiKey":["messages:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/audit/export":{"get":{"operationId":"getAuditExport","summary":"Export the audit log as a file","description":"Streams the whole chain as newline-delimited JSON, oldest first, with a header record describing how the hash is constructed and a trailer record carrying the event count and the verdict.\n\n`payload` is the exact text the database hashed and `occurredAt` the exact timestamp string. Hash them as given — re-serialising the parsed JSON changes the bytes and fails verification on an untouched record.\n\nA file with no trailer record is INCOMPLETE. The response status is sent before the last row is read, so a failure mid-transfer can only truncate the download; the trailer is how you tell a whole export from half of one.\n\nRequires an API key with `messages:read`, or a signed-in session.","tags":["Audit"],"security":[{"apiKey":["messages:read"]},{"session":[]}],"parameters":[{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},{"name":"until","in":"query","required":false,"schema":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/domains":{"get":{"operationId":"getDomains","summary":"List sending domains","description":"Requires an API key with `domains:read`, or a signed-in session.","tags":["Domains"],"security":[{"apiKey":["domains:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"post":{"operationId":"postDomains","summary":"Add a sending domain","description":"Returns the DNS records to publish. Mail cannot be sent until it verifies.\n\nRequires an API key with `domains:write`, or a signed-in session.","tags":["Domains"],"security":[{"apiKey":["domains:write"]},{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":3,"maxLength":253,"pattern":"^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$"}},"required":["name"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/domains/{id}":{"delete":{"operationId":"deleteDomainsById","summary":"Remove a domain","description":"Requires an API key with `domains:write`, or a signed-in session.","tags":["Domains"],"security":[{"apiKey":["domains:write"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/domains/{id}/verify":{"post":{"operationId":"postDomainsByIdVerify","summary":"Check the DNS records now","description":"Requires an API key with `domains:write`, or a signed-in session.","tags":["Domains"],"security":[{"apiKey":["domains:write"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/domains/{id}/setup":{"get":{"operationId":"getDomainsByIdSetup","summary":"The DNS records to publish","description":"Requires an API key with `domains:read`, or a signed-in session.","tags":["Domains"],"security":[{"apiKey":["domains:read"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/domains/{id}/cloudflare":{"post":{"operationId":"postDomainsByIdCloudflare","summary":"Publish the records via a connected Cloudflare account","description":"Requires an API key with `domains:write`, or a signed-in session.","tags":["Domains"],"security":[{"apiKey":["domains:write"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/account/cloudflare":{"delete":{"operationId":"deleteAccountCloudflare","summary":"Disconnect Cloudflare","description":"Requires an API key with `domains:write`, or a signed-in session.","tags":["Domains"],"security":[{"apiKey":["domains:write"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/inbound/addresses":{"get":{"operationId":"getInboundAddresses","summary":"List inbound addresses","description":"Requires an API key with `domains:read`, or a signed-in session.","tags":["Inbound"],"security":[{"apiKey":["domains:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"post":{"operationId":"postInboundAddresses","summary":"Create an inbound address","description":"Requires an API key with `domains:write`, or a signed-in session.","tags":["Inbound"],"security":[{"apiKey":["domains:write"]},{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string","minLength":3,"maxLength":320},"label":{"type":"string","maxLength":80}},"required":["address"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/inbound/addresses/{id}":{"delete":{"operationId":"deleteInboundAddressesById","summary":"Delete an inbound address","description":"Requires an API key with `domains:write`, or a signed-in session.","tags":["Inbound"],"security":[{"apiKey":["domains:write"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/inbound/messages":{"get":{"operationId":"getInboundMessages","summary":"List received messages","description":"Requires an API key with `messages:read`, or a signed-in session.","tags":["Inbound"],"security":[{"apiKey":["messages:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/inbound/messages/{id}":{"get":{"operationId":"getInboundMessagesById","summary":"Fetch a received message","description":"HTML is sanitised on the way out, without exception.\n\nRequires an API key with `messages:read`, or a signed-in session.","tags":["Inbound"],"security":[{"apiKey":["messages:read"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"delete":{"operationId":"deleteInboundMessagesById","summary":"Delete a received message","description":"Requires an API key with `domains:write`, or a signed-in session.","tags":["Inbound"],"security":[{"apiKey":["domains:write"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/suppressions":{"get":{"operationId":"getSuppressions","summary":"List suppressed addresses","description":"Every entry carries its cause: the reason, the receiving server’s own words, its status code, and the message whose bounce or complaint caused it. An entry added by hand has no causing message, and neither has one whose message has since aged out of retention.\n\nRequires an API key with `suppressions:read`, or a signed-in session.","tags":["Suppressions"],"security":[{"apiKey":["suppressions:read"]},{"session":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}},{"name":"before","in":"query","required":false,"schema":{"type":"string"}},{"name":"search","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":320}},{"name":"reason","in":"query","required":false,"schema":{"type":"string","enum":["hard_bounce","complaint","manual","unsubscribe","spam_trap"]}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"post":{"operationId":"postSuppressions","summary":"Suppress an address","description":"Requires an API key with `suppressions:write`, or a signed-in session.","tags":["Suppressions"],"security":[{"apiKey":["suppressions:write"]},{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string","minLength":3,"maxLength":320},"reason":{"type":"string","maxLength":500},"stream":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"}},"required":["address"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/suppressions/{address}":{"delete":{"operationId":"deleteSuppressionsByAddress","summary":"Remove a suppression","description":"Removes a `manual` or `unsubscribe` entry. A hard bounce cannot be removed this way (422 suppression_hard_bounce): re-sending to an address that does not exist is what costs a sender its reputation, and a genuinely stale one is lifted by support after review. A complaint or spam trap can never be removed at all (422 suppression_protected).\n\nRequires an API key with `suppressions:write`, or a signed-in session.","tags":["Suppressions"],"security":[{"apiKey":["suppressions:write"]},{"session":[]}],"parameters":[{"name":"address","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/webhooks":{"get":{"operationId":"getWebhooks","summary":"List webhook endpoints","description":"Requires an API key with `webhooks:read`, or a signed-in session.","tags":["Webhooks"],"security":[{"apiKey":["webhooks:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"post":{"operationId":"postWebhooks","summary":"Create a webhook endpoint","description":"Deliveries are signed; verify the signature before trusting a payload. Retried with backoff and idempotent on redelivery.\n\nRequires an API key with `webhooks:write`, or a signed-in session.","tags":["Webhooks"],"security":[{"apiKey":["webhooks:write"]},{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri"},"eventTypes":{"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}}},"required":["url"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/webhooks/{id}":{"delete":{"operationId":"deleteWebhooksById","summary":"Delete a webhook endpoint","description":"Requires an API key with `webhooks:write`, or a signed-in session.","tags":["Webhooks"],"security":[{"apiKey":["webhooks:write"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/alerts/rules":{"get":{"operationId":"getAlertsRules","summary":"List alert rules","description":"Every threshold on the account, with the live firing state and the last measurement. Also returns the vocabulary — the available kinds, their units and their defaults — so a client never has to hard-code them.\n\nRequires an API key with `alerts:read`, or a signed-in session.","tags":["Alerts"],"security":[{"apiKey":["alerts:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"post":{"operationId":"postAlertsRules","summary":"Create an alert rule","description":"With a body, creates one rule. With NO body, creates the default set for every kind — which is the \"switch alerting on\" call, because picking four thresholds before you have ever seen one fire is guesswork.\n\nOne rule per kind: a second one answers 409. Two rules watching one signal would mean two emails about it, which is the failure alerting exists to avoid.\n\n`threshold` is a fraction for the rate kinds (`0.003` is 0.3%) and a count for `dns_drift`. It is refused above the point at which the account would be suspended for the same numbers, since an alert that can only arrive after the suspension is not an alert.\n\nRequires an API key with `alerts:write`, or a signed-in session.","tags":["Alerts"],"security":[{"apiKey":["alerts:write"]},{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["complaint_rate","bounce_rate","quota_usage","dns_drift"]},"threshold":{"type":"number","minimum":0},"windowDays":{"type":"integer","minimum":1,"maximum":90},"cooldownMinutes":{"type":"integer","minimum":5,"maximum":43200},"notifyEmail":{"type":"boolean"},"notifyWebhook":{"type":"boolean"},"enabled":{"type":"boolean"}},"required":["kind","threshold"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/alerts/rules/{id}":{"patch":{"operationId":"patchAlertsRulesById","summary":"Change a threshold, a channel, or switch a rule off","description":"Editing a rule never re-arms it. An alert that is currently firing stays firing, and raising the threshold while it is open is not a way to be told about it a second time.\n\nRequires an API key with `alerts:write`, or a signed-in session.","tags":["Alerts"],"security":[{"apiKey":["alerts:write"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"threshold":{"type":"number","minimum":0},"windowDays":{"type":"integer","minimum":1,"maximum":90},"cooldownMinutes":{"type":"integer","minimum":5,"maximum":43200},"notifyEmail":{"type":"boolean"},"notifyWebhook":{"type":"boolean"},"enabled":{"type":"boolean"}}}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"delete":{"operationId":"deleteAlertsRulesById","summary":"Stop watching a signal","description":"Requires an API key with `alerts:write`, or a signed-in session.","tags":["Alerts"],"security":[{"apiKey":["alerts:write"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/alerts/events":{"get":{"operationId":"getAlertsEvents","summary":"What has fired, and where it went","description":"Newest first, keyset-paginated. Each row carries the value and the threshold AS THEY STOOD, not as they are now, plus which channels the notice was delivered on — so \"you never told us\" has an answer that survives somebody editing the rule afterwards.\n\nRequires an API key with `alerts:read`, or a signed-in session.","tags":["Alerts"],"security":[{"apiKey":["alerts:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/me":{"get":{"operationId":"getMe","summary":"Your account, plan and current sending allowance","description":"Requires an API key with `account:read`, or a signed-in session.","tags":["Account"],"security":[{"apiKey":["account:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/usage":{"get":{"operationId":"getUsage","summary":"Current usage against your allowance","description":"Requires an API key with `account:read`, or a signed-in session.","tags":["Account"],"security":[{"apiKey":["account:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/account/verify":{"get":{"operationId":"getAccountVerify","summary":"Whether the delivery record is intact","description":"The same verdict as `/v1/audit/verify`, in the older, narrower shape: `intact`, `brokenAt`, `problem`. Reachable with `account:read` because it returns no part of the log itself.\n\nRequires an API key with `account:read`, or a signed-in session.","tags":["Account"],"security":[{"apiKey":["account:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/api-keys":{"get":{"operationId":"getApiKeys","summary":"List API keys","description":"Requires an API key with `account:read`, or a signed-in session.","tags":["API keys"],"security":[{"apiKey":["account:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"post":{"operationId":"postApiKeys","summary":"Mint an API key","description":"The secret is returned once and never again.\n\nRequires a signed-in person with the **admin** role or higher — mint an API key. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["API keys"],"security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"scopes":{"minItems":1,"maxItems":20,"type":"array","items":{"type":"string","maxLength":40}}},"required":["name","scopes"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/team":{"get":{"operationId":"getTeam","summary":"List members and pending invitations","description":"Requires an API key with `team:read`, or a signed-in session.","tags":["Team"],"security":[{"apiKey":["team:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/sub-accounts":{"get":{"operationId":"getSubAccounts","summary":"List the accounts you pay for","description":"Returns what a payer is entitled to know — name, status and volume this month. Never the sub-account’s mail, domains or suppressions: the billing relationship is not a window into its data.\n\nRequires an API key with `account:read`, or a signed-in session.","tags":["Sub-accounts"],"security":[{"apiKey":["account:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"post":{"operationId":"postSubAccounts","summary":"Create a sub-account","description":"A fully isolated account with its own domains, keys and suppression list, billed to you and sharing your monthly allowance. It inherits your plan and cannot hold a subscription of its own.\n\nRequires a signed-in person with the **admin** role or higher — add an account to the bill. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Sub-accounts"],"security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200}},"required":["name"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/sub-accounts/{id}":{"get":{"operationId":"getSubAccountsById","summary":"One sub-account’s standing","description":"Requires an API key with `account:read`, or a signed-in session.","tags":["Sub-accounts"],"security":[{"apiKey":["account:read"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/streams":{"get":{"operationId":"getStreams","summary":"List message streams","description":"Requires an API key with `streams:read`, or a signed-in session.","tags":["Streams"],"security":[{"apiKey":["streams:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"post":{"operationId":"postStreams","summary":"Create a message stream","description":"Streams separate suppression scope and statistics, so a complaint on one stream can never suppress a password reset on another.\n\nRequires an API key with `streams:write`, or a signed-in session.","tags":["Streams"],"security":[{"apiKey":["streams:write"]},{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},"name":{"type":"string","minLength":1,"maxLength":80}},"required":["slug","name"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/templates":{"get":{"operationId":"getTemplates","summary":"List templates","description":"Requires an API key with `templates:read`, or a signed-in session.","tags":["Templates"],"security":[{"apiKey":["templates:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"post":{"operationId":"postTemplates","summary":"Create a template","description":"Requires an API key with `templates:write`, or a signed-in session.","tags":["Templates"],"security":[{"apiKey":["templates:write"]},{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"type":"string","maxLength":998},"html":{"type":"string","maxLength":5000000},"text":{"type":"string","maxLength":5000000},"variables":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]*$"},"required":{"type":"boolean"}},"required":["name"]}}}}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/templates/{id}":{"get":{"operationId":"getTemplatesById","summary":"Fetch a template","description":"Requires an API key with `templates:read`, or a signed-in session.","tags":["Templates"],"security":[{"apiKey":["templates:read"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"patch":{"operationId":"patchTemplatesById","summary":"Edit a template, publishing a new version","description":"A published version is immutable. Editing content publishes the next version and leaves the previous one byte-identical, so a send can always be traced to the exact content that went out.\n\nRequires an API key with `templates:write`, or a signed-in session.","tags":["Templates"],"security":[{"apiKey":["templates:write"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"type":"string","maxLength":998},"html":{"type":"string","maxLength":5000000},"text":{"type":"string","maxLength":5000000},"variables":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-zA-Z_][a-zA-Z0-9_]*$"},"required":{"type":"boolean"}},"required":["name"]}}}}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"delete":{"operationId":"deleteTemplatesById","summary":"Delete a template","description":"Requires an API key with `templates:write`, or a signed-in session.","tags":["Templates"],"security":[{"apiKey":["templates:write"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/templates/{id}/versions":{"get":{"operationId":"getTemplatesByIdVersions","summary":"List template versions","description":"Requires an API key with `templates:read`, or a signed-in session.","tags":["Templates"],"security":[{"apiKey":["templates:read"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/templates/{id}/preview":{"post":{"operationId":"postTemplatesByIdPreview","summary":"Render a template without sending","description":"Returns the rendered HTML and text for sample variables, and reports the same refusals a send would — so a preview cannot pass where the send fails.\n\nRequires an API key with `templates:read`, or a signed-in session.","tags":["Templates"],"security":[{"apiKey":["templates:read"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"integer","minimum":1,"maximum":9007199254740991},"variables":{"type":"object","propertyNames":{"type":"string","maxLength":64},"additionalProperties":{"type":"string","maxLength":5000}}}}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/templates/preview":{"post":{"operationId":"postTemplatesPreview","summary":"Render content without saving it","description":"Renders what you supply rather than what you have published, so an editor can show a template before it exists. Reports the same refusals and the same lint a send would, and stores nothing.\n\nRequires an API key with `templates:read`, or a signed-in session.","tags":["Templates"],"security":[{"apiKey":["templates:read"]},{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"type":"string","maxLength":1000},"html":{"type":"string","maxLength":1000000},"text":{"type":"string","maxLength":1000000},"variables":{"maxItems":100,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","maxLength":64},"required":{"type":"boolean"}},"required":["name"]}},"values":{"type":"object","propertyNames":{"type":"string","maxLength":64},"additionalProperties":{"type":"string","maxLength":5000}}}}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/billing":{"get":{"operationId":"getBilling","summary":"Subscription and plan","description":"Requires an API key with `billing:read` or `account:read`, or a signed-in session.","tags":["Billing"],"security":[{"apiKey":["billing:read"]},{"apiKey":["account:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/billing/history":{"get":{"operationId":"getBillingHistory","summary":"Billing history","description":"Requires an API key with `billing:read` or `account:read`, or a signed-in session.","tags":["Billing"],"security":[{"apiKey":["billing:read"]},{"apiKey":["account:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/billing/invoices":{"get":{"operationId":"getBillingInvoices","summary":"List invoices","description":"Requires an API key with `billing:read` or `account:read` (checked via `canReadBilling`), or a signed-in session.","tags":["Billing"],"security":[{"apiKey":["billing:read"]},{"apiKey":["account:read"]},{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/billing/invoices/{id}":{"get":{"operationId":"getBillingInvoicesById","summary":"Download an invoice","description":"Requires an API key with `billing:read` or `account:read` (checked via `canReadBilling`), or a signed-in session.","tags":["Billing"],"security":[{"apiKey":["billing:read"]},{"apiKey":["account:read"]},{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/api-keys/{id}":{"delete":{"operationId":"deleteApiKeysById","summary":"Revoke an API key","description":"Requires a signed-in person with the **admin** role or higher — revoke an API key. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["API keys"],"security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/account":{"patch":{"operationId":"patchAccount","summary":"Rename the account","description":"Requires a signed-in person with the **admin** role or higher — rename the account. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Account"],"security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120}},"required":["name"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/team/invites":{"post":{"operationId":"postTeamInvites","summary":"Invite somebody","description":"Requires a signed-in person with the **admin** role or higher — invite a colleague. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Team"],"security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","minLength":3,"maxLength":320},"role":{"type":"string","enum":["owner","admin","member","viewer"]}},"required":["email","role"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/team/{id}":{"patch":{"operationId":"patchTeamById","summary":"Change a member's role","description":"The person is signed out everywhere, because their permissions changed.\n\nRequires a signed-in person with the **admin** role or higher — change a colleague's role. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Team"],"security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["owner","admin","member","viewer"]}},"required":["role"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"delete":{"operationId":"deleteTeamById","summary":"Remove somebody from the account","description":"The last owner cannot be removed — an account with no owner has nobody who can administer it.\n\nRequires a signed-in person with the **admin** role or higher — remove a colleague. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Team"],"security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/billing/checkout":{"post":{"operationId":"postBillingCheckout","summary":"Start a checkout","description":"Requires a signed-in person with the **admin** role or higher — start a checkout. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Billing"],"security":[{"session":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string"},"interval":{"default":"month","type":"string","enum":["month","year"]},"profile":{"type":"object","properties":{"legalName":{"type":"string","minLength":1,"maxLength":200},"billingEmail":{"type":"string","maxLength":320,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"country":{"type":"string","minLength":2,"maxLength":2,"pattern":"^[A-Za-z]{2}$"},"taxIdKind":{"type":"string","enum":["gst","vat","abn","other"]},"taxId":{"type":"string","maxLength":64},"addressLine1":{"type":"string","maxLength":200},"addressLine2":{"type":"string","maxLength":200},"city":{"type":"string","maxLength":100},"region":{"type":"string","maxLength":100},"postalCode":{"type":"string","maxLength":32}},"required":["country"]}},"required":["plan","profile"]}}}},"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/billing/checkout/abandon":{"post":{"operationId":"postBillingCheckoutAbandon","summary":"Abandon a checkout in progress","description":"Requires a signed-in person with the **admin** role or higher — abandon a checkout. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Billing"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/billing/cancel":{"post":{"operationId":"postBillingCancel","summary":"Cancel the subscription at period end","description":"Requires a signed-in person with the **admin** role or higher — cancel the plan. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Billing"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/billing/pause":{"post":{"operationId":"postBillingPause","summary":"Pause the subscription","description":"Requires a signed-in person with the **admin** role or higher — pause the plan. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Billing"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/billing/resume":{"post":{"operationId":"postBillingResume","summary":"Resume a paused subscription","description":"Requires a signed-in person with the **admin** role or higher — resume the plan. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Billing"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/billing/profile":{"patch":{"operationId":"patchBillingProfile","summary":"Set the legal name and tax id printed on invoices","description":"Requires a signed-in person with the **admin** role or higher — change the billing details. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Billing"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}},"/v1/account/deletion":{"post":{"operationId":"postAccountDeletion","summary":"Request account deletion","description":"Right to erasure. Scheduled rather than immediate, so it can be cancelled.\n\nRequires a signed-in person with the **owner** role or higher — delete the account. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Account"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"get":{"operationId":"getAccountDeletion","summary":"Status of a deletion request","description":"Requires a signed-in session, acting on your own record — see whether a deletion is scheduled. Deliberately not role-gated, because the row being changed is the caller’s own.","tags":["Account"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}},"delete":{"operationId":"deleteAccountDeletion","summary":"Cancel a deletion request","description":"Requires a signed-in person with the **owner** role or higher — cancel a scheduled deletion. NOT reachable with an API key: this is an action where a leaked server-side credential would be catastrophic.","tags":["Account"],"security":[{"session":[]}],"responses":{"2XX":{"description":"Success."},"4XX":{"description":"The request was refused. `error.type` says why.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","description":"A stable machine-readable identifier. Branch on this, never on the message."},"message":{"type":"string"}},"required":["type","message"],"description":"Refusals that carry evidence add fields beside these two — the limit you would have to stay under, the address that was suppressed and why, the filename that was blocked. Additional properties are expected."}},"required":["error"]}}}}}}}}}