Web & Session API
The endpoints the web interface itself uses. Authenticate with a session token, or with a single inbox's own key.
Two ways to authenticate
| Header | Scope | Where it comes from |
|---|---|---|
| Authorization: Bearer <token> | The whole account | Returned by login/verify, what the browser uses |
| X-API-Key: mf_sk_… | One inbox only | The 🔑 button in that inbox's header |
Account keys do not work here
mf_live_… keys belong to the /api/v1 agent API. On these paths use a session token or that inbox's own key; an account key is refused with 403 even though the key itself is perfectly valid. The reverse is no longer true: an inbox key does work on /api/v1, where it sees only its own inbox.
Endpoints
Authregister · verify · login
| Method & path | Body | Returns |
|---|---|---|
| POST / | { email, password, username?, full_name? } | Sends a 6-digit code to the address |
| POST / | { email, otp } | { token, user } |
| POST / | { identifier, password } | A second-factor code, then { token, user } on verify |
identifier accepts either the username or the email address.
Inboxescreate · list · burn · delete
| Method & path | Body / query | Notes |
|---|---|---|
| POST / | { prefix, name?, group_name?, retention_hours?, subdomain?, domain_id? } | prefix is required here (1–50 chars). Encrypted automatically when the account has E2E on. |
| GET / | ?agent=1 | ?agent=0 | 1 = agent pool only, 0 = human pool only, omitted = both |
| POST / | none | Deletes every message but keeps the address alive |
| DELETE / | none | Releases the address itself, permanently |
API keyscreate · rotate · revoke · per-inbox key
| Method & path | Body / query | Notes |
|---|---|---|
| GET / | ?include_revoked=1 | Inventory. Keys come back masked, never in full |
| POST / | { label?, scopes?, expires_in_days? } | Account key. scopes defaults to inbox:read alone; an unknown name is rejected rather than dropped. Answers with the key in full, once. |
| POST / | none | New secret, same row. The old one stops working immediately |
| DELETE / | none | Revokes. The row stays so you can see when it happened |
| POST / | { scopes?, expires_in_days? } | Issues that inbox's mf_sk_… key. Refused if the inbox already has one, rather than replacing it silently |
Session token only, on purpose
These endpoints do not accept X-API-Key at all. Minting a key from a key would be a privilege ladder: a leaked narrow key could issue itself a wide one, and permissions would mean nothing.
Mailread · latest · tag filter · mark read · delete · send
| Method & path | Body / query | Notes |
|---|---|---|
| GET / | ?tag=promo | Each item carries id, from, subject, body, otp_code, is_read, direction, plus links, headers, attachments (metadata only, the bytes come from the attachment endpoint below) and spam (SpamAssassin score, threshold and the rules that fired; null means not scanned, which is not the same as clean) |
| GET / | none | Newest message, or { email: null } when the inbox is empty |
| POST / | none | Marks one message read |
| DELETE / | none | Deletes one message |
| GET / | none | The attached file itself. On an encrypted inbox it returns the envelope instead, because the server cannot open it. |
| POST / | { to, subject?, body?, html? } | Sends from this address, DKIM-signed. Counts against your monthly allowance. |
Calendarmeetings · schedule · answer · subscribe link
| Method & path | Body / query | Notes |
|---|---|---|
| GET / | ?include_cancelled=true | Meetings on the inbox calendar. Team members can read |
| POST / | { title, start, attendees, timezone?, duration_minutes? … } | Schedule and send invitations. Same body as the Agent API; owner or a member who can reply |
| PATCH / | only what changes | Move or edit a meeting this inbox organized |
| POST / | { message? } | Cancel it; guests are told |
| POST / | { response, comment? } | Answer an invitation you received |
| GET · POST · DELETE / | none | Subscribe link: read it (never creates one), create it, turn it off. Owner only; POST …/feed/rotate for a new link |
Working examples
# every message in an inbox
curl https://mailflat.net/api/inboxes/you@a7f2c.mailflat.net/emails \
-H "Authorization: Bearer <token>"
# → { "emails": [ { id, from, subject, body, otp_code, is_read }, … ] }
# newest message only, the OTP polling call
curl https://mailflat.net/api/inboxes/you@a7f2c.mailflat.net/emails/latest \
-H "Authorization: Bearer <token>"
# filter by tag (plus-addressing: you+promo@…)
curl "https://mailflat.net/api/inboxes/you@a7f2c.mailflat.net/emails?tag=promo" \
-H "Authorization: Bearer <token>"
# the same call with that inbox's own key
curl https://mailflat.net/api/inboxes/you@a7f2c.mailflat.net/emails/latest \
-H "X-API-Key: mf_sk_9d2f8a…"
# mark read · delete · send
curl -X POST .../emails/123/read -H "Authorization: Bearer <token>"
curl -X DELETE .../emails/123 -H "Authorization: Bearer <token>"
curl -X POST .../send -H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"to":"a@gmail.com","subject":"Hi","body":"Hello"}'
Line by linewhat each step of this curl example does
- /emails
- Everything in the inbox, newest first.
- /emails/latest
- One message, cheaper to poll than the full list.
- ?tag=promo
- Only mail sent to you+promo@…, so you can see who shared your address.
- X-API-Key: mf_sk_…
- Same routes, scoped to this one inbox. Safe to give to a third party.
OTP loop: call /emails/latest every 2–3s until otp_code is non-null, then stop. That is exactly what wait_for_otp does under the hood.
Status codes, rate limits & quota
Errors return { "detail": "…" } with the status below.
| Status | Meaning | Example detail |
|---|---|---|
| 200 | OK | request succeeded |
| 400 | Business rule rejected it | Monthly email quota reached |
| 401 | Missing / invalid credentials | A valid session token is required |
| 403 | Authenticated but not allowed | No inbox …@… for this API key… |
| 404 | That message or attachment is not in that inbox | Message 42 not found in inbox … |
| 422 | A field is missing, mistyped, or not a field at all | Invalid request: 'email' is required |
| 429 | Too many sign-in / sign-up attempts | Too many requests, please slow down |
Rate limit: only the anonymous auth endpoints are throttled, per IP. Sign-up and password reset 5/hour, login 10/minute, OTP verification 5/minute. Everything you do with a session token or an API key is bounded by your plan quota, not by a request ceiling, so polling for a message is fine.
Monthly quota: each plan has an allowance covering received and sent mail (see /api/plans); once spent, both directions are rejected with 400 Monthly email quota reached until the next month.
Retention: messages auto-purge after the inbox's window; the address itself is permanent.
Monthly quota: each plan has an allowance covering received and sent mail (see /api/plans); once spent, both directions are rejected with 400 Monthly email quota reached until the next month.
Retention: messages auto-purge after the inbox's window; the address itself is permanent.