{"openapi":"3.1.0","info":{"title":"1mail.dev API","version":"1.0.0","description":"Read-only REST for public, inbound-only disposable inboxes at 1mail.dev.\nNo signup, no accounts, no keys: anyone who knows an address can read it.\nMessages leave only via retention (24 h) or displacement.\n\nAll responses carry advisory `RateLimit-Limit` / `RateLimit-Remaining` /\n`RateLimit-Reset` headers (honest approximations; the limiter is\nper-location and eventually consistent). `429` adds `Retry-After`.\n\nA recommended read cadence is one poll per inbox every ≥ 2 s; `304`\nresponses replay cached bodies, so clients must honor `expires_at` locally.\n","contact":{"url":"https://github.com/1maildev/1maildev/issues","email":"contact@1mail.dev"}},"servers":[{"url":"https://api.1mail.dev/v1"}],"tags":[{"name":"messages","description":"Read inboxes and messages."}],"paths":{"/inboxes/{address}/messages":{"get":{"operationId":"listMessages","tags":["messages"],"summary":"List an inbox, newest first","description":"Returns `200` for any syntactically valid address — never-existed and\nall-expired are the same state. Reserved local parts behave as\npermanently empty inboxes. Keyset pagination via `since_id` (exclusive,\nnewer-than) and `older_than_id` (exclusive, page back); `limit`\ndefaults to 100, max 1000. Strong `ETag` derives from the newest\nmessage id only; send `If-None-Match` to poll for ~1 row read. Agents\nwaiting for mail should send `wait` instead of polling — the call\nholds until something newer than the pivot arrives (see the `wait`\nparameter).\n","parameters":[{"$ref":"#/components/parameters/Address"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/SinceId"},{"$ref":"#/components/parameters/OlderThanId"},{"$ref":"#/components/parameters/IfNoneMatch"},{"$ref":"#/components/parameters/Wait"}],"responses":{"200":{"description":"The page of messages (possibly empty).","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageList"}}}},"304":{"$ref":"#/components/responses/NotModified"},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/Internal"}}}},"/inboxes/{address}/messages/{id}":{"get":{"operationId":"getMessage","tags":["messages"],"summary":"Fetch one full parsed message","parameters":[{"$ref":"#/components/parameters/Address"},{"$ref":"#/components/parameters/MessageId"},{"$ref":"#/components/parameters/IfNoneMatch"}],"responses":{"200":{"description":"The full parsed message.","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FullMessage"}}}},"304":{"$ref":"#/components/responses/NotModified"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/Internal"}}}},"/inboxes/{address}/messages/{id}/raw":{"get":{"operationId":"getRawMessage","tags":["messages"],"summary":"Fetch the untouched original email","description":"The original bytes, decoded from storage. Served `message/rfc822` with\n`Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`\n— browsers never render mail on our origin.\n","parameters":[{"$ref":"#/components/parameters/Address"},{"$ref":"#/components/parameters/MessageId"}],"responses":{"200":{"description":"The raw email.","headers":{"Content-Disposition":{"description":"Always `attachment`; filename is `<id>.eml`.","schema":{"type":"string"}},"X-Content-Type-Options":{"description":"Always `nosniff`.","schema":{"type":"string"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"message/rfc822":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/Internal"}}}}},"components":{"parameters":{"Address":{"name":"address","in":"path","required":true,"description":"The full address as one path segment (`test@1mail.dev`; `@` may arrive percent-encoded). Local part is case-insensitive.","schema":{"type":"string"}},"MessageId":{"name":"id","in":"path","required":true,"description":"The UUIDv7 id minted at ingestion.","schema":{"type":"string","format":"uuid"}},"Limit":{"name":"limit","in":"query","description":"Page size. Default 100, max 1000.","schema":{"type":"integer","minimum":1,"maximum":1000,"default":100}},"SinceId":{"name":"since_id","in":"query","description":"Return messages strictly newer than this id (exclusive) — the poller's friend.","schema":{"type":"string","format":"uuid"}},"OlderThanId":{"name":"older_than_id","in":"query","description":"Return messages strictly older than this id (exclusive) — the viewer's friend.","schema":{"type":"string","format":"uuid"}},"IfNoneMatch":{"name":"If-None-Match","in":"header","description":"A previously seen `ETag`; yields `304` when nothing changed.","schema":{"type":"string"}},"Wait":{"name":"wait","in":"query","description":"Milliseconds to hold the call open (long-poll). Default 0 returns\nimmediately; max 30000. With `wait` > 0 the call resolves as soon as a\nlive message newer than the pivot exists: the pivot is `since_id` when\ngiven, else the request-time newest message — so a bare `wait` returns\nonly future arrivals, never the current page. An unknown or expired\n`since_id` resolves `[]` immediately. On timeout the response is\n`200 {\"messages\":[]}`, or `304` when the presented `If-None-Match`\nstill matches. Incompatible with `older_than_id` (`400`). A held call\ncounts as one read against the rate limits; retry the call to keep\nwaiting.\n","schema":{"type":"integer","minimum":0,"maximum":30000,"default":0}}},"headers":{"ETag":{"description":"Strong validator derived from the newest message id.","schema":{"type":"string"}},"RateLimitLimit":{"description":"Advisory read budget for the key (inbox or IP) this window.","schema":{"type":"integer"}},"RateLimitRemaining":{"description":"Advisory remaining reads this window.","schema":{"type":"integer"}},"RateLimitReset":{"description":"Advisory seconds until the window resets.","schema":{"type":"integer"}}},"responses":{"NotModified":{"description":"Nothing changed since the presented ETag.","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"BadRequest":{"description":"Bad address syntax or a non-1mail.dev domain.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotFound":{"description":"No live message with that id.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"RateLimited":{"description":"Read budget exhausted for the inbox or IP.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Internal":{"description":"Unexpected failure.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"schemas":{"EmailAddress":{"type":"object","required":["address"],"properties":{"address":{"type":"string"},"name":{"type":["string","null"]}}},"AttachmentMeta":{"type":"object","required":["filename","content_type","size"],"properties":{"filename":{"type":["string","null"]},"content_type":{"type":"string"},"size":{"type":"integer"}}},"Classification":{"type":"object","description":"What the service decided about this Message at ingest (#92): one\nobject, identical on the list item and the full message. `code` on the\nlist item is deliberate — an agent completes a verification in one\nlist call. Confidences are raw 0–1 probabilities, never thresholds:\nthe recommended reading is act at ≥ 0.8, treat 0.5–0.8 as possible —\nthe contract enforces nothing. A returned code or link is verbatim\nfrom the Message by construction (the model may only choose an\nextracted Candidate). Full option distributions and the model id stay\nin storage and are not exposed. Spam and scam are badges only —\nnothing is ever hidden or filtered on them.\n","required":["state","type","code","link","spam","scam","reason"],"properties":{"state":{"type":"string","enum":["classified","unavailable","skipped"],"description":"`classified` — the model answered. `unavailable` — the model could not answer within its bound; see `reason`. `skipped` — classification did not run: switch off, an unparsable Message, or a row that predates the feature.\n"},"type":{"type":["object","null"],"description":"The kind of email this Message is; null unless `classified`.","required":["value","confidence"],"properties":{"value":{"type":"string","enum":["verification","login","password_reset","welcome","account_notice","receipt","notification","marketing","personal","other"]},"confidence":{"type":"number","minimum":0,"maximum":1}}},"code":{"type":["object","null"],"description":"The code the recipient is meant to enter — normalized `value` to paste, `display` as written in the Message. Null when the Message carries none or the state is not `classified`.\n","required":["value","display","confidence"],"properties":{"value":{"type":"string"},"display":{"type":"string"},"confidence":{"type":"number","minimum":0,"maximum":1}}},"link":{"type":["object","null"],"description":"The link the recipient is meant to act on. Null when the Message carries none or the state is not `classified`.\n","required":["url","confidence"],"properties":{"url":{"type":"string","format":"uri"},"confidence":{"type":"number","minimum":0,"maximum":1}}},"spam":{"type":["number","null"],"minimum":0,"maximum":1,"description":"Raw confidence the Message is unsolicited junk mail; null unless `classified`. A badge, never a filter.\n"},"scam":{"type":["number","null"],"minimum":0,"maximum":1,"description":"Raw confidence the Message is a scam — impersonation, urgent credential or payment demands, brand/link-host mismatch; null unless `classified`. A badge, never a filter.\n"},"reason":{"type":["string","null"],"description":"Why there is no classification — `timeout` or `model_error` when `unavailable`; `disabled`, `unparsable` or `predates_classification` when `skipped`; null when `classified`.\n"}}},"MessageListItem":{"type":"object","required":["id","from","subject","snippet","received_at","expires_at","size","has_attachments","classification"],"properties":{"id":{"type":"string","format":"uuid","description":"UUIDv7 minted at ingestion."},"from":{"$ref":"#/components/schemas/EmailAddress"},"subject":{"type":"string"},"snippet":{"type":"string","description":"One-line preview — text part, else tag-stripped HTML; ≤200 chars."},"received_at":{"type":"string","format":"date-time","description":"RFC 3339 UTC; the worker clock","not the Date header.":null},"expires_at":{"type":"string","format":"date-time","description":"RFC 3339 UTC; deterministic 24 h TTL signal."},"size":{"type":"integer","description":"Bytes of the raw original."},"has_attachments":{"type":"boolean","description":"True when the original Message has one or more attachments; metadata only, no attachment bytes."},"classification":{"$ref":"#/components/schemas/Classification"}}},"MessageList":{"type":"object","required":["messages"],"properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/MessageListItem"}}}},"FullMessage":{"type":"object","required":["id","from","subject","snippet","received_at","expires_at","size","has_attachments","classification","to","message_id","headers","text","html","attachments"],"properties":{"id":{"type":"string","format":"uuid"},"from":{"$ref":"#/components/schemas/EmailAddress"},"to":{"type":"array","items":{"$ref":"#/components/schemas/EmailAddress"}},"subject":{"type":"string"},"snippet":{"type":"string"},"received_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"size":{"type":"integer"},"has_attachments":{"type":"boolean","description":"True when the original Message has one or more attachments; metadata only, no attachment bytes."},"classification":{"$ref":"#/components/schemas/Classification"},"message_id":{"type":["string","null"],"description":"The email's own Message-ID header — client correlation only, never identity."},"headers":{"type":"object","description":"Curated allowlist — message-id, date, reply-to, to, cc, list-unsubscribe, content-type.","additionalProperties":{"type":"string"}},"text":{"type":["string","null"]},"html":{"type":["string","null"],"description":"Unsanitized by design — treat as untrusted when rendering."},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/AttachmentMeta"}}}},"Problem":{"type":"object","description":"RFC 9457 problem details.","required":["type","title","status"],"properties":{"type":{"type":"string","format":"uri-reference"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string","format":"uri-reference"}}}}}}