API reference
The complete /v1 surface — every endpoint, parameter, and status code, stated as the server actually behaves. For the semantics behind them (TTLs, long-polling, extraction), each section links its concept page.
Conventions & auth
Base URL: https://api.mailfixture.com/v1 — the canonical API origin (requests to mailfixture.com/v1 answer 308 to it). Every request carries your key as a bearer token; create keys in Dashboard → API keys. Requests and responses are JSON; timestamps are RFC 3339 UTC; ids are UUIDs.
Authorization: Bearer mfx_your-key
Errors
Failures are RFC 7807 application/problem+json — status repeats the HTTP code, detail says what to fix:
{
"type": "about:blank",
"title": "forbidden",
"status": 403,
"detail": "custom domains require a paid plan"
}
Rate limits
Per key: 2 requests/second sustained, bursting to 60. Inbox creation has its own stricter bucket (bursts of 10, refilling 10/minute). Over either limit you get 429 with a Retry-After header — the SDKs absorb these automatically inside wait helpers.
Inboxes
Concepts: Inboxes & TTLs.
/v1/inboxesCreate an inbox. Body optional — empty gets a random address on the shared domain. Returns 201 with the inbox, including email_address.
local_partoptional — 1–64 chars of a-z0-9._-, no leading/trailing dot, lowercased. Omit for a random 10-char one. 409 if taken on the domain.domainoptional — a verified custom domain on your account; defaults to the shared domain. 404 if not yours.ttl_secondsoptional — 60 to 2,592,000 (30 days). Expired inboxes stop receiving immediately and are swept hourly. Omit = lives until deleted.Also: 403 when frozen or at the plan's active-inbox cap, 429 from the creation bucket.
/v1/inboxesList active inboxes: {"inboxes": [...]} with id, local_part, fqdn, ttl_expires_at, created_at, message_count.
/v1/inboxes/{id}Delete the inbox and all its messages. 204 on success, 404 if it isn't yours.
/v1/inboxes/{id}/clearDrop the messages, keep the address. 204.
/v1/inboxes/{id}/messagesList messages, newest last, as summaries (id, received_at, from_addr, to_addrs, subject, tls). This is the long-poll endpoint — see Messages.
waitoptional — seconds to hold the request until a matching message arrives. Clamped to 60; chain requests for longer waits (the SDKs do).sinceoptional — RFC 3339 timestamp; only messages received strictly after it. Use the last summary's received_at as a cursor.matchoptional — subject:, from:, or to: prefix + case-insensitive substring; a bare term matches the subject.Messages
Concepts: Messages, Extraction, Attachments.
/v1/messages/{id}Full detail: summary fields plus inbox_id, text_body, html_body, headers, extracted, attachments (metadata array), raw_size, expires_at. extracted includes auth — SPF/DKIM/DMARC verdicts for the delivering session (absent on messages received before verification shipped; see Extraction).
/v1/messages/{id}/otp{"best": "482913", "candidates": [...]} — best is null when no candidate clears the confidence threshold.
/v1/messages/{id}/links{"links": [...]} — each with url, text (anchor), class (verify | reset | unsubscribe | other).
/v1/messages/{id}/links/followFollow one of the message's extracted links server-side: we GET it (up to 5 redirects, https-only, public hosts only) and return {"requested_url", "final_url", "status", "redirects", "ok"} — status is the target's final answer (a remote 500 is a 200 from us with "status": 500), and the target's body is never returned. Only URLs present in the message's links can be followed.
classoptional — follow the first link of this class. Default when the body is empty: verify. 404 (listing the classes that exist) when there's no such link.indexoptional — 0-based index into the links array.urloptional — exact URL as it appears in links. At most one of class/index/url; 400 if more.Guard failures on the link itself are 400 (http:// target, private/internal host); an unreachable target, a redirect somewhere we refuse to go, or more than 5 redirects is 502.
/v1/messages/{id}/attachments/{index}Decoded bytes of one attachment; index is 0-based into the detail's attachments array. Always application/octet-stream with a forced-download disposition. 404 for an index past the array.
Phone numbers & SMS
Paid plans, US numbers, receive-only — the fine print lives in the SMS quickstart.
/v1/phone-numbersProvision a dedicated number. No body. 201 with id, phone_number (E.164), status (pending flips to active within about a minute), created_at, release_after (null). 403 on free plans or at the plan's number cap; 503 if the provider is unavailable.
/v1/phone-numbersList the account's numbers. release_after is non-null when a plan downgrade left the account over its number cap and the number is scheduled for automatic release — upgrade or release other numbers before that date to keep it.
/v1/phone-numbers/{id}Release the number (it goes back to the carrier pool — you won't get the same one again) and delete its messages. 204; 409 while the order is still pending.
/v1/phone-numbers/{id}/messagesLong-poll for texts — same wait/since as email. Summaries include the full text (the body is the message). match takes from:, to:, body:; a bare term matches the body — SMS has no subject.
/v1/sms/{id}Detail: id, phone_number_id, received_at, from_number, to_number, text, extracted, expires_at.
/v1/sms/{id}/otp · GET/v1/sms/{id}/links · POST/v1/sms/{id}/links/followByte-identical shapes and semantics to their email counterparts (including the follow endpoint's selectors and status codes).
Domains
Concepts: Custom domains. Paid plans only.
/v1/domainsRegister a custom domain: body {"fqdn": "testmail.acme.com"}. 201 with the domain plus setup — the TXT and MX records to publish (setup.txt.name/value, setup.mx.name/value/priority). 403 on the free plan; 409 if the domain is already registered.
/v1/domains{"domains": [...]} — your custom domains (with verified_at, mx_ok_at, and pending ones carrying setup) plus the shared domains.
/v1/domains/{id}/verifyRun the TXT + MX checks now. Returns txt_ok, mx_ok, verified_at, mx_ok_at, and setup. A background task also re-checks every 60s for the domain's first 7 days.
/v1/domains/{id}Remove the domain and every inbox and message on it. 204.
API keys
/v1/keysMint a key: body {"label": "ci"} (label optional). 201 with id, prefix, and key — the full mfx_ key appears in this response only; we store an argon2id hash.
/v1/keys{"keys": [...]} — metadata only (id, prefix, label, created_at), never key material.
/v1/keys/{id}Revoke the key immediately. 204.
Webhooks
Concepts: Webhooks — events, signatures, and the retry ladder. Paid plans only.
/v1/webhooksCreate an endpoint: {"url": "https://...", "description": "..."} (description optional; URL must be HTTPS on a public address). 201 with the endpoint and its whsec_ secret — shown in this response only. 403 on free plans or past the plan's endpoint cap; 409 for a duplicate URL.
/v1/webhooks{"webhooks": [...]} — with secret_hint (masked), consecutive_failures, disabled_at/disabled_reason.
/v1/webhooks/{id}/deliveriesDelivery history, most recent first: {"deliveries": [...]} with event_type, status, attempts, next_attempt_at, last_status_code, last_error, delivered_at. limit query param defaults to 50 (max 200); history is kept 7 days.
/v1/webhooks/{id}/testFire a signed endpoint.test event. 202 with {"delivery_id": "..."} — follow it in the delivery history. 409 if the endpoint is disabled.
/v1/webhooks/{id}/enableRe-enable an auto-disabled endpoint after fixing the receiver. 204.
/v1/webhooks/{id}Delete the endpoint; queued deliveries stop. 204.