Authentication
Every request carries an API key as a bearer token:
Authorization: Bearer day3_live_x7Kj9mP2...That is the only auth mode. The app’s session cookies work on its private
routes, never on /api/v1.
Keys
Admins create and revoke keys in the app under API keys. The API has no key-management endpoints: a key must not be able to mint another key.
A key belongs to one organization. The server resolves the account from the key, never from the request, so a key can’t reach another organization’s data even by id.
Format
day3_<env>_<40 characters>env is live or test. The prefix lets secret scanners recognise keys.
Only a SHA-256 hash is stored. A database leak never leaks a usable key, and a lost key can’t be recovered: revoke it and mint another. The app shows the first eight characters after the prefix so you can tell keys apart.
day3_test_... is reserved but not implemented. It returns
403 test_keys_not_supported instead of silently hitting live data. For
test-like behaviour, use a free organization in
sandbox mode.
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_api_key | Missing, malformed, or unknown key. |
| 401 | revoked_api_key | The key existed and was revoked. |
| 403 | test_keys_not_supported | A day3_test_ key was used. |
Scopes
A key with no scopes can read and write all content: audiences, contacts, fields, segments, topics, campaign drafts, and transactional sends.
Three actions reach beyond the account and can’t be undone, so they need a scope:
| Scope | Unlocks |
|---|---|
campaigns:send | POST /campaigns/{id}/send and POST /campaigns/{id}/schedule. |
automations:enroll | POST /automations/{id}/enroll. |
webhooks:manage | Every /webhooks route, reads included. |
All three are off by default and chosen when the key is created. You can’t add
a scope to an existing key. Mint a new one, so the grant stays visible in the
key list. A missing scope is 403 insufficient_scope; the fix is a new key.
Why these three:
- Sending to an audience. A script does what its author wrote; an agent with the same key decides for itself. “Email everyone on the list” is not a decision to hand over by default. Test sends are not scoped: they only reach addresses the caller names, and they are how an agent iterates on an email.
- Enrolling into an automation. The same decision spread over time: one call commits that person to every email in the flow. Listing automations is unscoped, since it names flows, not people. See Automations.
- Webhook endpoints. A standing feed of every address you mail, pushed to a URL the caller picks. Creating one is the API’s only write that can exfiltrate data. Reads are gated too, because the delivery log names every recipient.
Sandbox mode on free plans
Free organizations get full API access with two sending limits:
- Recipients must be members of your organization. Anyone else is
403 sandbox_recipient_not_allowed. - 100 emails a month. Past that is
403 plan_limit_reached.
Sandbox sends are real email, and responses carry "sandbox": true. Campaign,
test and API sends all draw from that one monthly pool. The product has exactly
one meter, and it counts emails.
Upgrading lifts both limits with no code change. You don’t need your own domain to try it: every new organization gets a day3 test address that sends to your team.
Free plans also cap the list at 500 subscribers. A contact write that would
cross it returns 403 plan_limit_reached, and a batch is rejected whole, never
partially applied.
Keeping a key safe
- Read it from an environment variable. Never hard-code or commit it.
- Never paste it into a prompt or chat tool. Tell your AI assistant to read
DAY3_API_KEYfrom the environment. - Use one key per environment, so revoking one causes no outage.
- Revoking is immediate. To rotate: mint the new key, deploy it, revoke the old one.