Authentication

Every request is authenticated with an API key in the Authorization header:

bash
Authorization: Bearer ssms_sk_test_...

Test and live keys

PrefixModeBehavior
ssms_sk_test_SandboxInstant and free. Messages are simulated ("simulated": true): no text is sent, except to your own verified phone. See Sandbox.
ssms_sk_live_LiveReal 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.

ScopeAllows
messages:sendPOST /v1/messages, POST /v1/test/inbound
messages:readGET /v1/messages, GET /v1/messages/{id}, GET /v1/deliverability
numbers:readGET /v1/numbers, GET /v1/numbers/available
numbers:writePOST /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
lookupGET /v1/lookup/{phone}
webhooksGET /v1/events, GET /v1/events/wait, POST /v1/events/{id}/replay, GET /v1/webhooks, GET /v1/webhooks/deliveries
webhooks:writePOST /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
billingGET /v1/balance, GET /v1/usage, GET/PATCH /v1/spend-limit
automationsPOST /v1/track, /v1/automations and everything under it
audit_logs:readGET /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)
keysGET/POST /v1/api_keys, DELETE /v1/api_keys/{id}

A restricted key calling outside its scopes gets:

json
{
  "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:

bash
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

StatusCodeMeaning
401invalid_api_keyMissing, malformed, revoked, or unknown key.
403key_pausedWe 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.
403live_access_requiredLive 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.
403insufficient_scopeA restricted key called an endpoint outside its scopes; required_scope names the one it needs.
403tenant_suspendedThe account is suspended.
403account_closedThe account is closed. Every key of a closed account is revoked too.
403email_unconfirmedThe 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.