Authentication
Every request is authenticated with an API key in the Authorization header:
Authorization: Bearer ssms_sk_test_...Test and live keys
| Prefix | Mode | Behavior |
|---|---|---|
ssms_sk_test_ | Sandbox | Instant and free. Messages are simulated ("simulated": true): no text is sent, except to your own verified phone. See Sandbox. |
ssms_sk_live_ | Live | Real numbers and real delivery. Mintable once your account has live access. |
Keys issued with an older prefix (dsms_sk_*, resms_sk_*)
are still accepted and will never be revoked for their prefix; new keys mint
as ssms_sk_.
Keys never expire, but you can roll or revoke them anytime from the console. Rolling revokes the old key immediately and mints a replacement.
Storage
We look keys up by a keyed hash (HMAC-SHA256 under a secret held on our servers). Each active key is also kept encrypted (AES-256-GCM, under a separate key held on our servers) so an admin can view and copy it again from the console. Every view is in the audit log. Revoking or rolling a key deletes its encrypted copy. Keys made before October 27, 2026 can't be viewed again: roll one to get a key you can.
Scopes
A key has full access unless you restrict it. In Console → API keys, choose Restricted and tick the scopes the key needs; a key that leaks can then only do what its job required. Rolling a key keeps its scopes.
| Scope | Allows |
|---|---|
messages:send | POST /v1/messages, POST /v1/test/inbound |
messages:read | GET /v1/messages, GET /v1/messages/{id}, GET /v1/deliverability |
numbers:read | GET /v1/numbers, GET /v1/numbers/available |
numbers:write | POST /v1/numbers, PATCH /v1/numbers/{id}, DELETE /v1/numbers/{id} |
verify | /v1/verify, /v1/verify/check, /v1/verify/{id} |
consent | /v1/consent and everything under it |
lookup | GET /v1/lookup/{phone} |
webhooks | GET /v1/events, GET /v1/events/wait, POST /v1/events/{id}/replay, GET /v1/webhooks, GET /v1/webhooks/deliveries |
webhooks:write | POST /v1/webhooks, GET/PATCH/DELETE /v1/webhooks/{id}, POST /v1/webhooks/{id}/rotate_secret, /test and /replay (creating an endpoint or rotating its secret returns the secret) |
customers | /v1/customers and everything under it |
contacts | /v1/contacts, /v1/lists, /v1/segments, /v1/topics and everything under them |
registrations | /v1/registrations and everything under it |
billing | GET /v1/balance, GET /v1/usage, GET/PATCH /v1/spend-limit |
automations | POST /v1/track, /v1/automations and everything under it |
audit_logs:read | GET /v1/audit-logs, GET /v1/exports/audit_log |
links | /v1/link-domains and everything under it (link shortening; sending with shorten_links needs messages:send) |
keys | GET/POST /v1/api_keys, DELETE /v1/api_keys/{id} |
A restricted key calling outside its scopes gets:
{
"error": {
"code": "insufficient_scope",
"message": "This API key is missing the `messages:send` scope. Use a key with that scope, or mint one in the console (API keys).",
"required_scope": "messages:send"
}
}Keys minted before scopes existed, and keys minted without choosing any, keep full access.
Keys for the apps you build
A key can create keys, so an agent building an app (in Replit, Claude Code or Cursor) can give the app its own key to keep as a secret:
curl https://api.jointextkit.com/v1/api_keys \
-H "Authorization: Bearer $TEXTKIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Booking app", "scopes": ["messages:send", "verify"]}'The response has the secret, once. A key only creates keys of its own mode
(a test key makes test keys) and never a broader one: a key with limited
scopes can only create keys with scopes inside its own. Every new key emails
the account's owner and admins and is in the audit log. GET /v1/api_keys
lists the keys of your mode; DELETE /v1/api_keys/{id} revokes one. 20 new
keys an hour.
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_api_key | Missing, malformed, revoked, or unknown key. |
| 403 | key_paused | We paused this live key because it suddenly made far more requests than usual from a country it had not been used from. An admin can resume it, or roll it, under API keys. See unusual key activity. |
| 403 | live_access_required | Live key used before live access was granted, a live-only endpoint hit with a test key, or a test key reaching data the whole account shares (contacts, consent, templates, customers, registrations, pools, the spend limit, the audit log) on an account with live access. |
| 403 | insufficient_scope | A restricted key called an endpoint outside its scopes; required_scope names the one it needs. |
| 403 | tenant_suspended | The account is suspended. |
| 403 | account_closed | The account is closed. Every key of a closed account is revoked too. |
| 403 | email_unconfirmed | The account's email isn't confirmed yet, so it can't create keys or webhooks or connect integrations. Confirm it in the console with the emailed code. |