Authentication

Search Documentation

Search across all developer documentation

Authentication

Every request to https://api.sendops.dev must carry a SendOps API key as a Bearer token. Keys are issued from the dashboard, scoped to one organization, and granted a fixed set of permissions chosen at creation time.

The Bearer header

Authorization: Bearer sk_live_KQX...ZJ7

No other authentication method is supported in v1. There are no session cookies, no signed-URL flow, no HMAC. If the header is missing, malformed, or the key has been revoked, the response is 401 Unauthorized (or 403 when the key is otherwise valid but is missing the required scope).

Key format

Keys are 32 random bytes (Base32-encoded, no padding) prefixed with their environment:

EnvironmentPrefixExample
Livesk_live_sk_live_KQX5...ZJ7
Testsk_test_sk_test_8GH2...P4Q

The first 8 characters after the prefix are stored in plaintext on our side so the dashboard and audit log can identify the key without exposing the secret. The secret itself is hashed (SHA-256) before storage — once you create a key, we can never show it to you again. If you lose it, you must rotate it.

Treat keys like passwords

Anyone with the raw key can call the API on your behalf. Store keys in your secrets manager, never in version control. If a key leaks, revoke it immediately from the dashboard.

Live vs. test keys

Both environments share the same data plane and scope vocabulary — there is no separate test sandbox. The difference is rate:

  • Live keys run at the full per-org rate limit (default 600 requests/minute).
  • Test keys run at 10% of the live rate (60 requests/minute).

All v1 endpoints are read-only, so the only practical difference is the rate ceiling. Use test keys for CI smoke checks and unit tests so noisy automation doesn’t burn your live budget.

Scopes

Scopes are SendOps ACL permission keys. The full catalogue lives at:

curl https://api.sendops.dev/v1/_scopes

Scope keys are namespaced under api.* so they cannot be confused with the dashboard’s internal ACL permissions. Every scope you can attach to a key starts with api..

ScopeWhat it unlocks
api.messages.viewGET /v1/messages, /v1/messages/{id}, /v1/messages/{id}/events. Recipient addresses are masked unless the key also holds api.messages.unmask_recipients.
api.messages.unmask_recipientsUnlocks the recipient= filter on /v1/messages and the full GET /v1/recipients/{email}/messages route (PII-gated)
api.reports.viewDeliverability, engagement, template-performance reports
api.suppressions.viewThe account suppression list and per-recipient lookup
api.undeliverable.viewThe SendOps-derived undeliverable list (permanent bounces, complaints, rejects) plus operator-cleared tombstones for ?since= polling
api.channels.viewChannels (SES configuration sets)
api.templates.viewEmail templates
api.tracking.viewTracking subdomains and their DNS state
api.identities.viewSES identities (email and domain)
api.account.viewOrganization snapshot via /v1/account

A request that hits an endpoint your key does not cover is rejected with 403 Forbidden and an RFC 7807 problem document. See Errors.

Least privilege

Pick the smallest scope set that solves your use case. Pulling deliverability dashboards into a status page only needs api.reports.view; reading a customer’s message history needs api.messages.unmask_recipients and you’ll want to think hard about who that key is given to.

Creating, rotating, and revoking keys

All lifecycle actions happen in the dashboard, in Settings → API Keys.

Create

Click New API Key, give it a name, choose Live or Test, and select scopes. The raw secret is displayed once — copy it now. If you lose it before storing it, immediately revoke this key and create a new one. There is no recovery path.

If your org has enforce 2FA turned on, creating a key requires a fresh MFA challenge.

Edit scopes

You can update the scope set on an existing key without rotating it. The change is applied immediately and recorded in the audit log (api_key.scopes_updated).

Rotate

Rotation issues a new secret while keeping the same name and scope set. Both the old key and the new key continue to work for 24 hours — a hard, non-negotiable grace window. After 24 hours the old key is automatically revoked by a background job.

Use rotation when:

  • You suspect a key may have leaked but want zero-downtime cutover.
  • You’re rolling keys on a routine schedule.

Rotation does not require MFA — it’s the safe path because the old key still works while you deploy the new one.

Revoke

Revocation is immediate. Once revoked, the key is dead — every subsequent request returns 401. Use this when:

  • You know a key has leaked.
  • You want to retire a key that is no longer in use.

If your org has enforce 2FA turned on, revocation requires a fresh MFA challenge.

Lifecycle in the audit log

Every key lifecycle event appears in the org audit log:

  • api_key.created — actor: the user who created it
  • api_key.scopes_updated — actor: the user who edited; includes added/removed scope diff
  • api_key.rotated — actor: the user who rotated; emitted on both the old and new key
  • api_key.revoked — actor: the user who revoked it (manual)
  • api_key.grace_expired — actor: system; emitted when the 24h rotation grace ends

Request-level activity (which key called which endpoint when) is not in the audit log — that lives in the observability stack (see operational dashboards in your org settings).

Storing keys safely

A few practical rules:

  • Put the key in your secrets manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Doppler — anything but a .env file in version control).
  • Inject the secret into your runtime via environment variable, never hard-code it.
  • Add the key prefix (sk_live_ or sk_test_) to your repository’s secret-scanning denylist.
  • Rotate on a cadence appropriate to your blast radius (we recommend at least annually for long-lived service keys, and immediately whenever a teammate with access leaves).