Detect disposable email in two lines
A REST (Representational State Transfer) API for your backend and a drop-in snippet for your forms. This reference is generated from the live OpenAPI schema, so it always matches the running API.
Quickstart
The base URL is https://api.isitdisposable.com. Create an account and generate a secret key from the dashboard, then check an address from your backend in your language of choice:
Send either an email or a domain (at least one is required). An email has its domain extracted; a bare domain is checked directly.
curl https://api.isitdisposable.com/v1/check \
-H "Authorization: Bearer sk_live_…" \
-H "content-type: application/json" \
-d '{"email": "[email protected]"}'A disposable address comes back like this:
{
"checked": true,
"normalized_email": "[email protected]",
"domain": "mailinator.com",
"disposable": true,
"mx_valid": true,
"role_account": false,
"relay": false,
"public_domain": false,
"spam_risk": false,
"mx_masked": false,
"did_you_mean": null,
"mx_records": ["mail.mailinator.com", "mail2.mailinator.com"],
"action": "block",
"reason": "disposable"
}action is one of allow, warn, or block; treat checked: false as allow.
The API fails open. Over quota or for an inactive subscription it still returns HTTP 200 with checked: false and action: "allow", so your form never breaks. Treat allow as do nothing.
Authentication
Send your key as a bearer token: Authorization: Bearer <key>. There are two kinds:
- sk_… secret keys are for server side use only. Keep them out of the browser and any public repository.
- pk_… publishable keys are safe in the browser. Each is restricted to the origins you list, so a lifted key only works from your approved sites. The snippet uses a publishable key.
API reference
Check one email or domain
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).
Request body
| Field | Type | Description |
|---|---|---|
| string | Email address to check. | |
| domain | string | Bare domain to check. |
Response
| Field | Type | Description |
|---|---|---|
| checkedreq | boolean | 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 | string | The submitted email, trimmed and lowercased. Null when only a domain was sent. |
| domain | string | The domain that was checked. |
| disposable | boolean | The core verdict: true when the domain belongs to a disposable or throwaway email provider. Null when the address was not checked. |
| mx_valid | boolean | Whether the domain has valid mail-exchange (MX) records and can receive mail. DNS-level only; no mailbox is ever probed. |
| role_account | boolean | Whether the local part is a role address (for example info@, support@, admin@) rather than a person. |
| relay | boolean | Whether the domain is a forwarding or relay or alias-masking provider. |
| public_domain | boolean | Whether the domain is a public consumer mailbox provider (for example gmail.com). |
| spam_risk | boolean | 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 | boolean | 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 | string | 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 | string[] | The resolved MX hostnames, or an empty list when there are none. Null on a fail-open response (when checked is false). |
| actionreq | string | 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 | string | 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. |
Errors
- 400: Invalid request: missing input.
- 401: Missing or revoked API key.
- 403: Publishable key used from an Origin not on its allowlist.
- 429: Rate limit exceeded; retry after the Retry-After header.
Check up to 100 emails or domains
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).
Request body
| Field | Type | Description |
|---|---|---|
| itemsreq | CheckRequest[] | The addresses or domains to check; at most 100 per batch. |
Response
| Field | Type | Description |
|---|---|---|
| resultsreq | CheckResponse[] | - |
| summaryreq | BatchSummary | - |
Errors
- 400: Invalid request: missing input.
- 401: Missing or revoked API key.
- 403: Publishable key used from an Origin not on its allowlist.
- 429: Rate limit exceeded; retry after the Retry-After header.
Unsubscribe Lifecycle Email
Opt an account out of lifecycle (marketing) email via its per-account unsubscribe token. Never affects transactional email (account, security, billing, quota): those are a separate registry category (app.email.registry) that app.email.client.send_email's marketing guard never gates on the lifecycle opt-out at all. A malformed token is rejected by Pydantic (422) before this handler runs. An unknown token is a generic 404 (see the module docstring). A known token sets lifecycle_opt_out_at = now() only if it is not already set (idempotent second call, same response). An audit_log row is written on every successful call (actorless: this is a public, unauthenticated action, so there is no user session to attribute it to -- the account id is the target, mirroring app.identity.account_deletion's own actorless system-audit rows).
Request body
| Field | Type | Description |
|---|---|---|
| tokenreq | string | - |
Response
| Field | Type | Description |
|---|---|---|
| statusreq | string | - |
Schemas
The request and response objects, as the API returns them.
BatchRequest
| Field | Type | Description |
|---|---|---|
| itemsreq | CheckRequest[] | The addresses or domains to check; at most 100 per batch. |
BatchResponse
| Field | Type | Description |
|---|---|---|
| resultsreq | CheckResponse[] | - |
| summaryreq | BatchSummary | - |
BatchSummary
| Field | Type | Description |
|---|---|---|
| countreq | integer | - |
CheckRequest
| Field | Type | Description |
|---|---|---|
| string | Email address to check. | |
| domain | string | Bare domain to check. |
CheckResponse
| Field | Type | Description |
|---|---|---|
| checkedreq | boolean | 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 | string | The submitted email, trimmed and lowercased. Null when only a domain was sent. |
| domain | string | The domain that was checked. |
| disposable | boolean | The core verdict: true when the domain belongs to a disposable or throwaway email provider. Null when the address was not checked. |
| mx_valid | boolean | Whether the domain has valid mail-exchange (MX) records and can receive mail. DNS-level only; no mailbox is ever probed. |
| role_account | boolean | Whether the local part is a role address (for example info@, support@, admin@) rather than a person. |
| relay | boolean | Whether the domain is a forwarding or relay or alias-masking provider. |
| public_domain | boolean | Whether the domain is a public consumer mailbox provider (for example gmail.com). |
| spam_risk | boolean | 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 | boolean | 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 | string | 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 | string[] | The resolved MX hostnames, or an empty list when there are none. Null on a fail-open response (when checked is false). |
| actionreq | string | 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 | string | 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. |
ErrorDetail
| Field | Type | Description |
|---|---|---|
| reasonreq | string | - |
| message | string | - |
ErrorResponse
| Field | Type | Description |
|---|---|---|
| detailreq | ErrorDetail | - |
StatusResponse
| Field | Type | Description |
|---|---|---|
| statusreq | string | - |
UnsubscribeRequest
| Field | Type | Description |
|---|---|---|
| tokenreq | string | - |
Snippet install guide
Drop one tag into your page. It watches your email fields and checks them as the visitor types, enforcing the mode you choose. No build step.
<script src="https://cdn.isitdisposable.com/v1/snippet.js"
data-key="pk_live_…"
data-mode="warn"
async></script>Attributes
| Attribute | Purpose | Default |
|---|---|---|
| data-key | Your account publishable key (pk_...) | required |
| data-mode | block, warn, or flag (flag only sets a hidden field and fires the callback) | warn |
| data-selector | Which fields to watch | all email inputs |
| data-message | Message shown on warn or block | a sensible default |
| data-callback | Global function called with the full result | none |
| data-debounce | Milliseconds to wait after typing | 600 |
The dashboard generates a snippet pre-filled with your publishable key under Snippet.
Official SDKs
Prefer a native Software Development Kit (SDK) to raw HTTP calls? Official clients wrap the API above for Python and TypeScript, plus a Model Context Protocol (MCP) server so an MCP capable assistant can call it as a tool.
Python SDK
Synchronous and asynchronous clients with typed results, for single and batch checks.
pip install isitdisposableTypeScript SDK
Zero runtime dependencies, for Node.js 18 and later plus edge runtimes, with typed results.
npm install isitdisposableMCP server
Gives an MCP capable assistant tools to check one email, one domain, or up to 100 at once. Listed in the official MCP registry as io.github.richelo/isitdisposable-mcp.
npx -y isitdisposable-mcpAll three fail open by default. On network trouble they return a not checked result with action: "allow" and log a warning, so a signup form using them never breaks because a check could not complete.
Errors
Errors return a JSON (JavaScript Object Notation) body of the shape { detail: { reason, message } }. A 429 includes a Retry-After header. Detection problems never raise an error; they fail open with an allow.
Rate limits are applied per second, with a ceiling per API key and a higher ceiling per account; they are currently the same on every plan. If you need more throughput, contact support.
For AI agents
Point your agent or client generator at the machine-readable specification. It is the same schema this page is built from, scoped to the public endpoints:
GET https://api.isitdisposable.com/v1/openapi.jsonThe quickest integration for an agent is the official MCP server, which hands an MCP capable assistant tools to check email addresses and domains directly.