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/.
X-API-Key: sf_live_abc12345.XYZ_your_secret_here
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:
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.
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.
| Plan | Monthly emails |
|---|---|
| Starter | 50 (BYOC) |
| BYOC | Unlimited* |
| Growth | 25,000 |
| Pro | 100,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.
{
"detail": "Authentication credentials were not provided."
}Best practices
| Practice | Why |
|---|---|
| One key per environment | Rotate or revoke a single env's key without affecting others. |
| Use environment variables | Never hardcode keys in source code or config files. |
| Set expiry dates for short-lived tasks | Automation keys have a known lifespan - enforce it. |
| Revoke immediately on exposure | If 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 need | Removed a service? Delete its key so stale credentials don't linger in the dashboard. |
| Monitor for 401 spikes | A 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.
Error response when scope is violated
{
"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."
}