{"openapi":"3.1.0","info":{"title":"isitdisposable API","description":"Disposable email address detection. Authenticate server side with a secret key (`Authorization: Bearer sk_...`) or from the browser with a publishable key (`pk_...`) restricted to your allowed origins. Over quota or for an inactive subscription the API fails open: HTTP 200 with `checked` false and `action` \"allow\", so a customer form never breaks.","version":"1.0"},"servers":[{"url":"https://api.isitdisposable.com"}],"paths":{"/v1/email/unsubscribe":{"post":{"tags":["email"],"summary":"Unsubscribe Lifecycle Email","description":"Opt an account out of lifecycle (marketing) email via its per-account\nunsubscribe token.\n\nNever affects transactional email (account, security, billing, quota):\nthose are a separate registry category (app.email.registry) that\napp.email.client.send_email's marketing guard never gates on the\nlifecycle opt-out at all.\n\nA malformed token is rejected by Pydantic (422) before this handler\nruns. An unknown token is a generic 404 (see the module docstring). A\nknown token sets lifecycle_opt_out_at = now() only if it is not already\nset (idempotent second call, same response). An audit_log row is\nwritten on every successful call (actorless: this is a public,\nunauthenticated action, so there is no user session to attribute it to\n-- the account id is the target, mirroring\napp.identity.account_deletion's own actorless system-audit rows).","operationId":"unsubscribe_lifecycle_email_v1_email_unsubscribe_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnsubscribeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/check":{"post":{"tags":["v1"],"summary":"Check one email or domain","description":"Run detection on a single email address or bare domain. Send `email` or `domain`; at least one is required. An email has its domain extracted and checked; a bare domain is checked directly. When the account is over quota or not in a serving state the response fails open: HTTP 200 with `checked` false and `action` \"allow\", so a customer's form keeps working. The snippet treats `action` \"allow\" as do nothing. The same fail-open shape with `reason` \"overloaded\" is returned instantly when this origin is saturated with in-flight checks, instead of queuing the request (ISID-400).","operationId":"check_endpoint_v1_check_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckResponse"}}}},"400":{"description":"Invalid request: missing input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Publishable key used from an Origin not on its allowlist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded; retry after the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/check/batch":{"post":{"tags":["v1"],"summary":"Check up to 100 emails or domains","description":"Check up to 100 addresses synchronously. Items are independent: an invalid item is reported and never billed without failing the batch. A request over the cap is rejected at validation time with HTTP 422. When this origin is saturated with in-flight checks, the whole batch fails open instantly instead of queuing: every item returns `checked` false, `action` \"allow\", `reason` \"overloaded\", and nothing is billed (ISID-400).","operationId":"check_batch_endpoint_v1_check_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResponse"}}}},"400":{"description":"Invalid request: missing input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Publishable key used from an Origin not on its allowlist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded; retry after the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"BatchRequest":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CheckRequest"},"type":"array","maxItems":100,"title":"Items","description":"The addresses or domains to check; at most 100 per batch."}},"type":"object","required":["items"],"title":"BatchRequest"},"BatchResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/CheckResponse"},"type":"array","title":"Results"},"summary":{"$ref":"#/components/schemas/BatchSummary"}},"type":"object","required":["results","summary"],"title":"BatchResponse"},"BatchSummary":{"properties":{"count":{"type":"integer","title":"Count"}},"type":"object","required":["count"],"title":"BatchSummary"},"CheckRequest":{"properties":{"email":{"anyOf":[{"type":"string","maxLength":320},{"type":"null"}],"title":"Email","description":"Email address to check."},"domain":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Domain","description":"Bare domain to check."}},"type":"object","title":"CheckRequest"},"CheckResponse":{"properties":{"checked":{"type":"boolean","title":"Checked","description":"Whether the address was actually evaluated. False on a safe fail-open response (for example over quota or an inactive subscription); when false, treat the result as allow and ignore the signals."},"normalized_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Normalized Email","description":"The submitted email, trimmed and lowercased. Null when only a domain was sent."},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain","description":"The domain that was checked."},"disposable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Disposable","description":"The core verdict: true when the domain belongs to a disposable or throwaway email provider. Null when the address was not checked."},"mx_valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Mx Valid","description":"Whether the domain has valid mail-exchange (MX) records and can receive mail. DNS-level only; no mailbox is ever probed."},"role_account":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Role Account","description":"Whether the local part is a role address (for example info@, support@, admin@) rather than a person."},"relay":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Relay","description":"Whether the domain is a forwarding or relay or alias-masking provider."},"public_domain":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Public Domain","description":"Whether the domain is a public consumer mailbox provider (for example gmail.com)."},"spam_risk":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Spam Risk","description":"Whether the domain appears on a spam or abuse reputation list. Opt-in, off by default, and never affects the core disposable verdict."},"mx_masked":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Mx Masked","description":"Whether the domain routes mail through a known consumer forwarding service (for example Cloudflare Email Routing) that masks the real mail system. Opt-in, off by default, and never affects the core disposable verdict. A failed or empty list read yields false, never an error, and the default policy maps this signal to allow."},"did_you_mean":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Did You Mean","description":"A suggested correction when the domain looks like a typo of a common provider (for example gmial.com for gmail.com), or null."},"mx_records":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Mx Records","description":"The resolved MX hostnames, or an empty list when there are none. Null on a fail-open response (when checked is false)."},"action":{"type":"string","title":"Action","description":"The recommended action for this result under the account's enforcement policy: one of allow, warn, or block. The platform never blocks on your behalf; your snippet or caller applies it."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"A short code explaining the action or verdict. Examples: disposable, mx_invalid, relay, role_account, public_domain, spam_risk, mx_masked (the signal that fired), dns_unavailable, quota_exceeded, subscription_inactive, account_blocklist, account_allowlist, internal_error (a batch item that failed unexpectedly and was never billed), overloaded (the origin shed the request under load; fail-open, never billed, safe to retry). Treat unknown codes gracefully, since the set can grow. Null when no specific reason applies."}},"type":"object","required":["checked","action"],"title":"CheckResponse"},"ErrorDetail":{"properties":{"reason":{"type":"string","title":"Reason"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["reason"],"title":"ErrorDetail"},"ErrorResponse":{"properties":{"detail":{"$ref":"#/components/schemas/ErrorDetail"}},"type":"object","required":["detail"],"title":"ErrorResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"StatusResponse":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"StatusResponse","description":"Generic {\"status\": \"...\"} response used by many routers."},"UnsubscribeRequest":{"properties":{"token":{"type":"string","format":"uuid","title":"Token"}},"type":"object","required":["token"],"title":"UnsubscribeRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}