Authentication — API keys, scopes, and the Bearer header
Every request to https://api.mails.ai authenticates with an API key in the standard bearer header:
Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxThere is no other auth scheme — no query-string key, no x-api-keyheader. A request with no key, or a malformed one, gets a structured 401 that tells you exactly what went wrong:
{
"error": {
"type": "authentication_error",
"code": "missing_authorization",
"message": "Missing Authorization header. Use `Authorization: Bearer mk_live_...` or sign in for dashboard access.",
"param": null,
"request_id": "req_01KVF46QACE5C361SBYTP6JE0S"
}
}Key format
Keys are formatted mk_{mode}_{32 hex chars}, where {mode} is live or test:
mk_live_…— sends real email, meters real usage and cost.mk_test_…— exercises the full API shape without sending real mail or incurring cost. Use it in CI and local dev.
The full key is shown exactly once, at creation. mails.ai stores only a SHA-256 hash — it cannot show you the key again, so save it immediately. If you lose it, rotate (mint a new one, revoke the old).
Getting a key
Sign in at app.mails.ai and mint a key under API keys, or create one programmatically once you already hold a key with the manage scope:
curl https://api.mails.ai/v1/api-keys \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "inbound-worker",
"scopes": ["read"],
"mode": "live"
}'{
"id": "key_01J9X2K7Q8...",
"key": "mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"prefix": "mk_live_xxxx",
"mode": "live",
"scopes": ["read"],
"agent_id": null,
"name": "inbound-worker",
"expires_at": null,
"created_at": "2026-06-19T17:30:00.000Z"
}During the closed beta, request your first key from support@mails.ai.
Scopes
Every key carries a set of scopes. mails.ai is secure-by-default: a key with an empty scope set can authenticate but is authorized for nothing. The three scopes map cleanly to what a request does:
send— send, reply, forward, and create drafts that send.read— list and fetch messages, events, threads, reputation, and usage.manage— create and revoke keys, manage agents and webhooks, edit suppression, cancel or reschedule messages.
A key may also be bound to a single agent via agent_id at creation, so a leak is contained to one identity. GET /v1/meechoes the calling key’s scopes, mode, and bound agent — a useful sanity check:
curl https://api.mails.ai/v1/me -H "Authorization: Bearer $MAILS_API_KEY"Sending requires an approved workspace
Authentication and authorization are necessary but not sufficient to send. A newly created workspace starts unapproved: it can authenticate, create agents and keys, and read — but any send returns 403 workspace_not_approved until the workspace is approved. This is how mails.ai keeps the shared sending pool clean. During the beta, request approval from support@mails.ai.
Rotation and revocation
Keys never expire unless you set expires_at. To rotate, mint the replacement, deploy it, then revoke the old one:
curl -X DELETE https://api.mails.ai/v1/api-keys/key_01J9X2K7Q8... \
-H "Authorization: Bearer $MAILS_API_KEY"Revocation takes effect immediately — the next request with that key gets 401 revoked_api_key. An expired key gets 401 expired_api_key; an unknown key gets 401 invalid_api_key.
Handling keys safely
- Never commit a key. Load it from an environment variable (
MAILS_API_KEYis the convention across the SDKs and MCP server) or a secrets manager. Treat it like a password. - Scope minimally. A service that only ingests webhooks needs
read, notmanage. A send-only worker needssend. - Bind to an agentwhere it helps — a per-agent key means a leak revokes one identity, not your whole workspace.
- Use test mode in CI.
mk_test_keys never send real mail, so your test suite can exercise the full request and response shape with zero blast radius.
Common questions
What's the difference between live and test mode?
A key is either mk_live_ or mk_test_. Test-mode keys exercise the full API shape without sending real email or incurring cost — use them in CI and local development. Live-mode keys send real mail and meter real usage. The mode is fixed at key creation and visible in the key prefix and in GET /v1/me.
How do I rotate a key?
Mint the replacement first (POST /v1/api-keys or the dashboard), deploy it, then revoke the old one with DELETE /v1/api-keys/{id}. Revocation is immediate — the next request with the revoked key gets 401 revoked_api_key. Because a key is only shown once at creation, there is no way to recover a lost key; rotate instead.
Should I use one key for everything?
No. Scope each key to the least it needs (a webhook-ingesting service only needs read; a send-only worker only needs send) and, where it helps, bind a key to a single agent via agent_id so a leak is contained to one identity. Keys are cheap to mint and free to revoke.