Authentication
Every API request needs two headers: your secret key and your organization ID.
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx
X-JKASMS-Org-Id: ORG_XXXXXXXXXXXXBearer . Starts with sk_test_ or sk_live_.ORG_.Why two headers?
The organization ID is not redundant. It scopes the key lookup to one account, which lets us verify your key against a single record rather than every key that shares a prefix.
Key verification uses argon2id, which is deliberately slow — that is what makes a stolen key database useless. An unscoped lookup would turn that deliberate slowness into a denial-of-service vector.
The three values a key gives you
Creating a key shows you a secret and a webhook secret once. Two other identifiers stay available on the API keys page forever — knowing which is which saves a support ticket.
| Value | Secret? | What it is for |
|---|---|---|
sk_… secret key | Yes — shown once | Authenticates every request, in the Authorization header. |
whsec_… webhook secret | Yes — shown once | Verifies the signature on webhooks we send you. See Verifying webhooks. |
ORG_… organization ID | No | Scopes the key lookup, in the X-JKASMS-Org-Id header. |
pk_… key ID | No | You never send it. We send it to you, as X-JKASMS-Key-Id on every webhook, so you can tell which key a callback belongs to. |
Test and live keys
| Prefix | Behaviour |
|---|---|
sk_test_ | Full pipeline, nothing reaches a handset, nothing is charged. Works immediately, even while your account is under review. |
sk_live_ | Real messages, real money. Can only be created once your account is approved. |
Both use the same base URL. There is no separate sandbox host — the key prefix alone decides.
Keeping keys safe
- Keep keys server-side. A key in browser or mobile app code is public, whatever it is wrapped in.
- Use a test key in development, and a separate live key per environment, so revoking one does not take down the others.
- Revoke immediately if a key is exposed. Revocation takes effect on the next request.
IP allowlisting
Optional, and off by default. Once enabled in Settings, requests from any other address are refused even with a valid key.
Ranges broader than /16, and private ranges, are rejected — an allowlist that matches everything is not an allowlist. You cannot enable it with an empty list, because that would lock you out of your own API.
Errors
| Status | Code | Cause |
|---|---|---|
| 401 | INVALID_API_KEY | Missing, malformed, revoked or wrong key |
| 403 | ACCOUNT_SUSPENDED | Account suspended, or a live key used before approval |
| 403 | FORBIDDEN | Request IP is not in your allowlist |