Get started
Authentication and API keys
Every request carries an API key. Test keys run the same checks as live keys without sending or charging anything, and each key’s permissions limit what it can reach.
Authentication
All API requests require a Bearer token in the Authorization header. Use test keys (mk_test_...) for development: no real SMS are sent and nothing is charged. Use live keys (mk_live_...) for production.
Test mode
A test key (mk_test_...) works from the moment your account is created, before any top-up. New accounts start PENDING_PAYMENT: a live key gets 402 PAYMENT_REQUIRED for everything except a few onboarding reads (GET /v1/accounts/me, GET /v1/accounts/:id, GET /v1/accounts/:id/topup-allowance and GET /v1/pricing); test keys pass regardless.
A test send runs the same recipient checks a live send does: CASL consent and opt-out, the API key's allow and deny lists, the reserved-exchange and undeliverable-number checks, and the Canadian-destination check. It skips owning the from number, owner phone verification, your balance, the sending limits, and the DNCL check (not enforced on any send yet). No message reaches a carrier: it is priced exactly like a live one, so you see the real cost, then the row is marked delivered.
A test key is a sandbox with no live reach: it cannot buy or release phone numbers, mint, rotate or revoke live keys, send the owner phone verification code, run right-to-erasure, change webhooks, file a volume or phone-number allowance request, or change a live key's lists or default-deny, and it cannot read the dead-letter queue. Message and verification history through a test key show test rows only. Contacts, groups, lists, consents and opt-outs stay writable, except one a live key's allow or deny list references: changing that needs a live key too.
API keys & permissions
Every API key carries a permissions object, one string of letters per resource: r (read, GET), w (write, POST), m (modify, PATCH or PUT), d (delete, DELETE). A missing resource or an empty string means no access to it. You can also send the key as an X-API-Key header instead of an Authorization Bearer token. A few routes need a less obvious letter: POST /v1/compliance/erasure needs compliance:d; POST /v1/accounts/:id/phone-verification and its /confirm need account:m; POST /v1/compliance/dncl/check needs compliance:r; DELETE /v1/contact-groups/:id/members/:contactId needs contact_groups:m; and DELETE /v1/accounts/:id/api-keys/:keyId/lists/:mode needs api_keys:m.
The resources a key's permissions object can name:
| Resource | Meaning |
|---|---|
| messages | Send and read SMS messages |
| phone_numbers | Provision, search and release phone numbers |
| contacts | Manage contacts |
| contact_groups | Manage contact groups and broadcasts |
| lists | Manage the allow and deny contact lists |
| compliance | CASL consents, opt-outs and DNCL checks |
| webhooks | Manage webhook endpoints |
| verify | Send OTPs and check verifications |
| account | View or update the account profile and usage |
| api_keys | Manage API keys |
| emails | Send and read email |
| email_domains | Manage email domains |
| email_suppressions | Manage email suppression lists |
| email_templates | Manage stored email templates |
A new key defaults to the permissions of the key that created it: omit permissions entirely and it gets exactly what the caller has. Asking for more takes a password step-up (an X-Step-Up-Token header with the api_keys:elevate scope), or the request is refused with 403 PERMISSION_ESCALATION. A test key can only create test keys.
HonkIO