Authentication

How bearer API keys work, which scopes they can carry, and how to read 401 vs 403 responses.

Every request carries a credential in the Authorization header:

curl https://api.mailfully.com/v1/emails \
  -H "Authorization: Bearer mf_live_xxxxxxxxxxxx"

Create keys at dashboard.mailfully.com/api-keys.

Key format

API key secrets follow mf_<env>_<body>, where <env> is live or test and <body> is 43 base62 characters ([0-9A-Za-z]). A full secret is 51 characters.

Mailfully stores only a SHA-256 hash of the secret, its first 12 characters, and its last 4. The dashboard identifies each key by that prefix and last 4. The plaintext is shown exactly once, when the key is created or rotated. Copy it then; you cannot retrieve it later.

Test and live environments

Each key belongs to one environment, and the environment separates your message data: messages, their events, analytics rollups, and the keys themselves are scoped to both your org and the key's environment. A test key never sees live messages: a live message id fetched with an mf_test_ key returns 404 not_found, indistinguishable from a message that never existed. Templates, domains, webhooks, and suppressions are shared resources: both environments of your org see the same ones.

Test sends are exempt from the verified-domain gate and never count against your monthly quota. The daily send cap applies to both environments.

Scopes

Seven scopes exist. Four can be minted onto API keys; the three manage:* scopes are session-only, and an API key can never carry them.

ScopeUnlocksOn API keys
sendPOST /v1/emails and /v1/emails/batch, cancel and reschedule; template create, update, delete; domain create, verify, tracking; also satisfies email, template, and domain readsYes (the default)
read:emailsGET /v1/emails, GET /v1/emails/{id}, GET /v1/emails/{id}/events; also template and domain readsYes
read:analyticsAnalytics readsYes
read:suppressionsSuppression list readsYes
manage:keysAPI key list, create, revoke, rotateNo — session only
manage:webhooksWebhook endpoint CRUD, deliveries, dead letters, replayNo — session only
manage:suppressionsAdd and remove suppressionsNo — session only

Keys default to ["send"] when you omit scopes at creation.

Read routes accept any one of their listed scopes: GET /v1/emails requires send or read:emails, so a send-only key can read the messages it sends; template and domain reads accept either scope the same way. Mint read:*-only keys for dashboards and reporting jobs that should never send.

Webhook management, suppression add/remove, and API key management are dashboard-session operations today. Calling them with an mf_ key returns 403 insufficient_scope regardless of how the key was minted. Manage these at dashboard.mailfully.com.

Dashboard sessions get scopes from the member's role: owners and admins hold all seven, members hold everything except manage:keys, and viewers hold only the three read:* scopes.

401 vs 403

A 401 means the credential itself failed. A 403 means the credential is valid but not allowed to do this. Authentication runs before scope checks, so a bad credential always returns 401 even when a scope problem also exists.

StatustypeWhen
401missing_api_keyNo Authorization header
401invalid_api_keyHeader is not Bearer <token>, or an mf_ key that is unknown or too short
401invalid_session_tokenA bearer that does not start with mf_ and fails session verification
403invalid_api_keyA recognized key that has been revoked
403insufficient_scopeAuthenticated, but missing the scope the route requires

Any bearer that does not start with mf_ is treated as a dashboard session token, so a pasted value that lost its mf_ prefix surfaces as 401 invalid_session_token rather than invalid_api_key.

{
  "error": {
    "type": "invalid_api_key",
    "message": "The provided API key is invalid."
  }
}

Rotation

Rotating a key mints a new secret and keeps the old one authenticating for a 24-hour grace window. After the window the old key fails with 403 invalid_api_key ("This API key has been revoked."). Deploy the new secret before the window closes.

Rotation requires manage:keys, so it is a dashboard-session operation — rotate from the dashboard's API keys page. See Rotate an API key for the endpoint contract.