Skip to content

Learn

Authentication

Every request except the OpenAPI document is authenticated with an API key carrying a fixed set of scopes.

Sending your key

Send the key as a bearer token. This is the form the API is built around and the one you should use.

Authorization: Bearer sk_live_a1b2c3d4.Xj9kQ2mZ7pR4tYv1WbN6cS0dF3gH8jK5lM2nP7qR4tY

If a gateway in front of your application strips or rewrites the Authorization header, SMSend also accepts the key in a dedicated header. Authorization is checked first.

X-API-Key: sk_live_a1b2c3d4.Xj9kQ2mZ7pR4tYv1WbN6cS0dF3gH8jK5lM2nP7qR4tY

What a key looks like

A key is a public prefix and a secret, joined by a dot. The prefix identifies which key is being used; the secret proves you hold it.

sk_live_a1b2c3d4.Xj9kQ2mZ7pR4tYv1WbN6cS0dF3gH8jK5lM2nP7qR4tY
└──── prefix ───┘ └────────────── secret ──────────────┘
  stored in clear     only a SHA-256 hash is stored

SMSend stores the prefix in clear and only a SHA-256 hash of the secret. The full value is shown exactly once, when the key is created, and is unrecoverable afterwards. If you lose it, create a new key and revoke the old one.

Scopes

A key carries a fixed set of scopes chosen when it is created. Give each integration the narrowest set it needs — a key that only reads reports should never be able to spend money.

ValueMeaning
MESSAGES_SEND
Submit a batch. This is the only scope that can spend money.
MESSAGES_READ
Read batches, individual messages, and the catch-up feed.
ACCOUNT_READ
Read the wallet balance, credit limit and billing mode.
SENDER_IDS_READ
List the sender IDs registered to the account.
WEBHOOKS_MANAGE
Create, rotate, enable and disable webhook endpoints.

Scopes are re-checked on every request

When authentication fails

Every authentication failure returns the same 401 with the same message — an unknown key, a wrong secret, a revoked key, an expired key and a suspended account are indistinguishable from outside. Which one it was appears only in SMSend's own logs. This is deliberate: a more helpful error would confirm to an attacker which half of a guessed credential was right.

401 Unauthorized
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Provide a valid API key as `Authorization: Bearer <key>`.",
    "retryable": false,
    "detail": {},
    "request_id": "01K3F7XQZ8V2N4M6P8R0T5CJWE"
  }
}

A valid key that lacks the scope for an endpoint is a 403, not a 401. The credential is fine; it is the permission that is missing, and the response names the scope you need.

403 Forbidden
{
  "error": {
    "code": "INSUFFICIENT_SCOPE",
    "message": "This API key does not carry the `SENDER_IDS_READ` scope.",
    "retryable": false,
    "detail": { "required_scope": "SENDER_IDS_READ" },
    "request_id": "01K3F7XQZ8V2N4M6P8R0T5CJWE"
  }
}

Rotating a key

An account may hold several active keys at once, and that is the supported state — it is what lets you rotate without a window where nothing works.

  1. Create a second key with the same scopes.
  2. Deploy it, and confirm traffic is flowing on the new key.
  3. Revoke the first key. Revocation is a status change, not a deletion, so the audit trail survives.