API Authentication
Machine-to-machine requests authenticate with an API key. Dashboard sessions authenticate with a short-lived JWT. Both are accepted on the same endpoints; a key is what you want for integrations and scripts.
Quick Start
Send your key in the X-API-Key header:
X-API-Key: ddmarc_<64 hex characters>Or as a bearer token, which is handy for clients that only expose an Authorization header:
Authorization: Bearer ddmarc_<64 hex characters>If both headers are present, X-API-Key takes precedence.
Getting Your API Key
Open API key settings
In the dashboard, go to Settings → Organization → API Keys. Only organization admins can create or revoke keys.
Create a key with the right scope
Give it a descriptive name (for example "Production sync" or "CI pipeline"), pick a scope, and optionally set an expiry in days. Scope defaults to read.
Copy and store it securely
The full key is returned once, at creation. Only a short prefix is stored for display, so it cannot be shown again. Put it in a secrets manager.
Key Management Endpoints
Keys can also be managed over the API. These routes require an admin session or an admin-scoped key.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/api-keys | List API keys |
| POST | /api/v1/api-keys | Create an API key |
| DELETE | /api/v1/api-keys/{key_id} | Revoke an API key |
POST /api/v1/api-keyscurl -X POST "https://api.ddmarc.com/api/v1/api-keys" \
-H "X-API-Key: $DDMARC_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "CI pipeline",
"scope": "read",
"expires_in_days": 90
}'{
"id": 7,
"name": "CI pipeline",
"key": "ddmarc_4f1c...b93a",
"key_prefix": "4f1c8ad2",
"scope": "read",
"expires_at": "2026-10-17T09:14:00Z",
"created_at": "2026-07-19T09:14:00Z"
}key is present only on this 201 response. Subsequent GET calls return key_prefix, scope, last_used_at and expires_at only, alongside the plan allowance fields limit, used and can_create.
Making Authenticated Requests
curl -X GET "https://api.ddmarc.com/api/v1/domains" \
-H "X-API-Key: $DDMARC_API_KEY"const response = await fetch(
"https://api.ddmarc.com/api/v1/domains",
{ headers: { "X-API-Key": process.env.DDMARC_API_KEY } }
);
const domains = await response.json(); // bare arrayimport os, requests
response = requests.get(
"https://api.ddmarc.com/api/v1/domains",
headers={"X-API-Key": os.environ["DDMARC_API_KEY"]},
)
domains = response.json() # list[dict]API Key Properties
Key format
A key is the literal prefix ddmarc_followed by 64 lowercase hex characters (32 random bytes) — 71 characters in total. Anything not starting with ddmarc_ is rejected outright.
Authority
A key belongs to the organization, not to the person who created it, and it always resolves to the same organization context. What it may actually do is bounded by its scope — not by the creator's role.
Expiration
Keys do not expire by default. Pass expires_in_days at creation to set one. An expired key stops authenticating immediately.
Rotation
There is no in-place regenerate. Rotate by creating a second key, deploying it, then revoking the old one — which also means you need a spare slot under your plan's key cap.
Scopes
Scope is chosen when the key is created and cannot be changed afterwards. Scopes are hierarchical: admin satisfies anything that requires write, which satisfies anything that requires read. A request that needs more authority than the key carries fails with 403.
| Scope | Level | Grants |
|---|---|---|
| read | Default | Read-only access to domains, reports, senders and analytics. |
| write | Mutating | Everything in read, plus creating and modifying domains, senders, rules and uploads. |
| admin | Full | Everything in write, plus administrative routes such as team management. |
Grant the narrowest scope that works. A read key that leaks cannot delete a domain.
Plan Limits
Programmatic API access requires the Protect plan or higher. Requests are metered hourly and pooled across the whole organization rather than per key.
| Plan | Request limit | Keys |
|---|---|---|
| Monitor | No API access | — |
| Protect | 1,000 requests / hour | 1 key |
| Growth | 5,000 requests / hour | 3 keys |
| Professional | 10,000 requests / hour | 10 keys |
| Partner Starter | 25,000 requests / hour | Unlimited |
| Partner | 25,000 requests / hour | Unlimited |
| Enterprise | Custom | Unlimited |
Hitting the hourly cap returns 429 with Retry-After: 3600. No X-RateLimit-* headers are sent on successful responses, so track usage on your side.
Authentication Errors
| Code | detail | Cause |
|---|---|---|
| 401 | Invalid API key | Key is unknown, malformed, expired or revoked. All four produce the same message, so a 401 never confirms that a key ever existed. |
| 401 | Not authenticated | No credential was sent at all. |
| 403 | API key requires 'write' scope or higher | The route needs more authority than the key's scope. |
| 403 | API access requires the Protect plan or higher. | Returned when creating a key on a plan without API access. |
| 429 | API rate limit exceeded (...) | Hourly org-wide cap reached. Includes Retry-After: 3600. |
{
"detail": "Invalid API key"
}Errors are always a flat object with a detail field. There is no nested errorobject and no machine-readable error code — branch on the HTTP status.
JWT Sessions
The dashboard and the mobile view authenticate with JWTs rather than API keys. You only need this if you are building something that signs a human in.
- Access tokens are short-lived. They expire after 15 minutes and are sent as
Authorization: Beareror as an httpOnlyaccess_tokencookie. - Refresh tokens last 7 days and rotate. Call
POST /api/v1/auth/refreshto exchange one; each refresh token is single-use, so replaying an old one fails. - Several sign-in methods exist. Password, magic link, one-time code, passkeys (WebAuthn), and Google or Microsoft single sign-on, all under
/api/v1/auth. - Scopes do not apply to JWTs.A signed-in user's authority comes from their role. Scope enforcement is specific to API keys.
Security Best Practices
- Never expose keys in client-side code. API keys should only be used server-side.
- Use environment variables. Never hardcode API keys in your source code.
- Ask for the smallest scope. Most integrations only ever read. Reserve write and admin for the jobs that mutate.
- Set an expiry on throwaway keys. A key issued for a migration or a one-off script should not outlive it.
- Watch last-used timestamps. Each key records
last_used_at; a key that has gone quiet is a key you can revoke. Creation and revocation are written to the activity log.
Managing API Keys
List
See every key with its name, prefix, scope, creation date and last-used timestamp, plus how many of your plan's key slots are in use.
Revoke
Revoking deletes the key record outright. It stops working immediately and cannot be restored — issue a new key instead.
Rotate
Create, deploy, then revoke. Keep the two keys overlapping long enough that nothing is mid-request when the old one goes away.