Docs/Authentication
Getting Started

Authentication

All API requests are authenticated with an API key passed in the X-API-Key request header. Keys are SHA-512 hashed before storage - the raw secret is shown exactly once at creation.

Sending your key

Pass your API key in the X-API-Key header on every request to /api/send/.

HTTP header
X-API-Key: sf_live_abc12345.XYZ_your_secret_here
Never expose API keys in client-side code. Keys must only be used from your server or backend. If a key is exposed, revoke it immediately from the dashboard.

Security model

SHA-512 hashing

When you create a key, we generate a random secret, show it to you once, then store only its SHA-512 hash. On each request, we hash the incoming key and compare against the stored hash using a constant-time comparison. This means:

  • A database breach cannot expose your raw secrets
  • Lookup is fast - a prefix index (first 8 chars) narrows the DB query
  • Lost keys cannot be recovered - generate a new one instead

Key structure

Every key has two parts separated by a dot:

Key anatomy
abc12345.XYZ_32_random_chars_here_XXXXXXXXXXXXXXXX
┗━━━━━━┛ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
  prefix        secret (32-char random, never stored)

The prefix (8 characters) is stored in plaintext and displayed in your dashboard - it helps you identify which key is which. The secret is hashed with SHA-512 and never stored in recoverable form.

Expiry dates

When creating a key you can set an optional expiry date. After the expiry, the key is automatically rejected with a 401 Unauthorized. Expired keys are not automatically deleted - you can see them in your dashboard.

Revocation

Revoke any key instantly from Dashboard → API Keys. Revocation is propagated within ~2 minutes via a Redis cache. Once revoked, a key stops authenticating immediately (up to the cache window) but the record is retained in your dashboard — it can be seen by admins and is kept for audit purposes.

The 2-minute revocation window exists because we cache key hashes for performance. In practice, most revocations propagate within seconds. Plan for up to 2 minutes if timing is critical.

Deleting a key

From Dashboard → API Keys each key also has a Delete action. Delete is a hard delete — the row, its stored hash, and its domain restrictions are removed from your account permanently. This cannot be undone, and unlike revocation the key cannot be restored by an admin.

Use delete when you want a key fully cleared from your records. Use revoke when you just need to disable a key quickly but want to keep the audit trail (for example, while rotating a key, or when investigating a suspected leak). A deleted key stops working immediately and is purged from the authentication cache at the same time.

Rate limiting

Requests are rate-limited per API key. The default limit is 60 requests per minute for all plans.

When you exceed the limit, the API returns 429 Too Many Requests with a standard error body. Back off and retry after 1–5 seconds.

PlanMonthly emails
Starter50 (BYOC)
BYOCUnlimited*
Growth25,000
Pro100,000

* Unlimited means no SendFleet cap - your AWS SES quota applies.

Failed authentication

Requests with a missing, invalid, or revoked key return 401 Unauthorized. Repeated failed attempts from the same IP are automatically blocked after 10 failures within 60 seconds.

401 Unauthorized
{
  "detail": "Authentication credentials were not provided."
}

Best practices

PracticeWhy
One key per environmentRotate or revoke a single env's key without affecting others.
Use environment variablesNever hardcode keys in source code or config files.
Set expiry dates for short-lived tasksAutomation keys have a known lifespan - enforce it.
Revoke immediately on exposureIf a key leaks, disable it at once. Revoke to stop it fast; delete to also purge it from your records.
Delete keys you no longer needRemoved a service? Delete its key so stale credentials don't linger in the dashboard.
Monitor for 401 spikesA sudden spike may indicate a key is being probed.

Domain-scoped API keys

API keys can be optionally restricted to a specific set of sending domains. When a key has domain restrictions, it can only send from those domains — any attempt to send from a different domain returns a 403 Forbidden with the error code domain_not_allowed.

How it works

  • No restrictions (default): A key with no domain restrictions can send from all domains in your account — this is the backwards-compatible behavior for existing keys.
  • Scoped to specific domains: When you select one or more domains, the key can only send from those exact domains. Any other domain is rejected.
  • Domain deletion: If a domain is deleted, it is automatically removed from all key restrictions. If a key loses all its restrictions this way, it silently regains access to all domains.

Setting domain scope

Domain scope is configured when creating an API key from the Dashboard → API Keys → New key dialog. You can select specific domains or leave the selector empty for full access. Admins can also manage domain scope from the Django admin panel.

Scope is enforced on every send. The domain check happens at the routing layer before any email is queued. It applies to both Shared SES and BYOC sending paths.

Error response when scope is violated

403 - Domain not allowed
{
  "success": false,
  "error":   "domain_not_allowed",
  "detail":  "API key 'Production' is not authorized to send from 'other.com'. Allowed domains: example.com, marketing.example.com."
}