Reference
Errors
Every error the API returns has an HTTP status and a stable code. General codes can come from any call; the others belong to one product.
General error codes
Every error is a flat JSON object: code, message (follows Accept-Language), messageEn, messageFr, statusCode, and an optional details field with more about what failed. A malformed request, such as a missing field or a value outside its documented bounds, returns 422 VALIDATION_ERROR with details naming the field. A request the framework itself rejects before your code runs, such as invalid JSON (400), a body over the size limit (413) or an unsupported content type (415), returns a Fastify FST_ERR_* code with an English-only message instead.
| HTTP | Code | Meaning |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 401 | API_KEY_REVOKED | This API key has been revoked. Use a different key. |
| 402 | INSUFFICIENT_BALANCE | Account balance too low |
| 402 | PAYMENT_REQUIRED | The account has not completed its first top-up. Live keys are blocked until then except a few account and pricing reads; test keys are unaffected. |
| 403 | FORBIDDEN | The API key lacks the permission this action needs, skip_consent_check was sent with a live key, the account is suspended or closed (see details.reason), or the id in the path belongs to another account |
| 403 | LIVE_KEY_REQUIRED | A test key was used for something only a live key can do. |
| 403 | PERMISSION_ESCALATION | A new or rotated key asked for permissions broader than the caller's own, without a password step-up. |
| 403 | KEY_FENCED | This key is restricted by its own allow or deny lists or by default-deny, so it cannot change those lists or the groups and contacts they include, change any key’s list settings, create keys, or rotate any key but itself. |
| 403 | ACCOUNT_NOT_VERIFIED | The account has not verified an owner phone number yet, required before a live send. |
| 403 | SENDING_PAUSED | Live sending is paused on this account (details show when it resumes and why) |
| 404 | NOT_FOUND | No resource matches the given id. |
| 409 | DEAD_LETTER_ALREADY_REPLAYED | This event was already replayed, or a replay of it is in progress. Nothing was sent, and retrying won't help. |
| 409 | CONFLICT | A resource with that identifier already exists, or a key rotation failed because the key was already revoked, is a session key, or another request rotated it first |
| 422 | VALIDATION_ERROR | Request body or query failed validation (see details) |
| 422 | WEBHOOK_LIMIT_REACHED | Reached the limit of 10 webhook endpoints per account. Delete one before registering another |
| 429 | RATE_LIMITED | Too many requests. Slow down |
| 500 | INTERNAL_ERROR | An unexpected server error occurred. Retry |
| 502 | WEBHOOK_REPLAY_FAILED | The endpoint did not accept the replay. The event stays in the dead-letter queue |
| 503 | SERVICE_UNAVAILABLE | New registrations are temporarily disabled (POST /v1/accounts), or phone verification is temporarily unavailable (POST /v1/accounts/:id/phone-verification). Retry later |
Codes that belong to one product have their own sections below: SMS error codes and email error codes.
SMS error codes
The codes below are specific to SMS, phone numbers, verification and CASL. Any call can also answer with the codes in the general section above, such as VALIDATION_ERROR, INSUFFICIENT_BALANCE and NOT_FOUND.
| HTTP | Code | Meaning |
|---|---|---|
| 403 | NUMBER_LIMIT_REACHED | Account has reached its phone number limit |
| 403 | PHONE_NUMBER_NOT_OWNED | The from number is not provisioned on this account. |
| 403 | ALLOW_LIST_BLOCKED | Recipient is not on the API key's ALLOW list |
| 403 | DENY_LIST_BLOCKED | Recipient is on the API key's DENY list |
| 403 | SEND_LIMIT_REQUEST_TOO_EARLY | Volume requests open once the account's probation period is over. |
| 404 | VERIFICATION_NOT_FOUND | Verification ID not found or not owned by this account |
| 404 | CONTACT_NOT_FOUND | Contact not found |
| 404 | CONTACT_GROUP_NOT_FOUND | Contact group not found |
| 404 | CONTACT_GROUP_MEMBER_NOT_FOUND | That contact is not a member of this group |
| 404 | CONTACT_LIST_NOT_FOUND | Contact list not found |
| 404 | CONTACT_LIST_ENTRY_NOT_FOUND | Contact list entry not found |
| 409 | VERIFICATION_ALREADY_VERIFIED | This number has already been verified |
| 409 | PURCHASE_IN_PROGRESS | Another number purchase is in flight. Retry shortly |
| 409 | ALLOWANCE_REQUEST_PENDING | An allowance request is already awaiting review |
| 409 | PHONE_NUMBER_SUSPENDED | This number's monthly charge could not be collected. Top up to reactivate it. |
| 409 | SEND_LIMIT_REQUEST_PENDING | A volume request is already awaiting review. |
| 409 | IDEMPOTENCY_KEY_REUSED | The same Idempotency-Key was sent with a different to, from or body. |
| 409 | CONTACT_ALREADY_EXISTS | A contact with that phone number or email already exists |
| 409 | CONTACT_IN_USE | Remove the contact from its groups and lists before clearing its phone number |
| 409 | CONTACT_GROUP_ALREADY_EXISTS | A contact group with that name already exists |
| 409 | CONTACT_GROUP_MEMBER_EXISTS | Contact is already a member of this group |
| 409 | CONTACT_LIST_ALREADY_EXISTS | A contact list with that name already exists |
| 409 | CONTACT_LIST_ENTRY_EXISTS | This entry already exists in the list |
| 410 | VERIFICATION_EXPIRED | The verification code has expired |
| 422 | NON_CANADIAN_NUMBER | Not a valid Canadian E.164 number |
| 422 | UNDELIVERABLE_NUMBER | The number failed at the carrier three times running (for any customer); refused without charge for 90 days. |
| 422 | NOT_A_MOBILE_NUMBER | The destination is a landline or VoIP number. The carrier refuses it before sending; nothing is charged. |
| 422 | RESERVED_DESTINATION | The destination is in a reserved exchange (555-XXXX, N11, carrier test codes). Refused before anything is sent; nothing is charged. |
| 422 | MESSAGE_TOO_LONG | Body would exceed the carrier limit of 10 SMS parts (≈1,530 GSM-7 or 670 Unicode characters). Nothing is charged |
| 422 | VERIFICATION_INVALID_CODE | Incorrect code. attempts_remaining shows how many tries are left |
| 422 | INVALID_ALLOWANCE_REQUEST | Requested allowance must exceed your current limit |
| 422 | LINK_SHORTENER_BLOCKED | Link shorteners are refused because carriers filter them. Use the full URL |
| 422 | BROADCAST_TOO_LARGE | Contact group too large for one broadcast (250 members by default). Split it and send in batches |
| 422 | CANNOT_ERASE_OWN_NUMBER | That number belongs to your account. Erasure is only for a subscriber's number |
| 422 | TOO_MANY_AREA_CODES | Search at most 25 area codes at a time |
| 422 | INVALID_PHONE_NUMBER | Not a valid E.164 phone number |
| 422 | CONTACT_LIST_ENTRY_INVALID | Each entry must specify exactly one of phone_number, contact_id or contact_group_id |
| 422 | CONTACT_HAS_NO_PHONE | The contact has no phone number, so it cannot join an SMS group or list |
| 429 | RECIPIENT_RATE_LIMITED | More than 30 messages to one recipient in an hour or 100 in a day; retry after the Retry-After header. |
| 429 | VERIFICATION_MAX_ATTEMPTS | Too many wrong attempts. This verification is locked |
| 429 | DAILY_LIMIT_REACHED | Daily sending limit reached (details show your limit and count). Request a higher volume once eligible |
| 429 | FANOUT_LIMIT_REACHED | This exact message already reached the maximum number of distinct recipients allowed in 24 hours |
| 429 | NUMBER_RATE_LIMITED | This sending number reached its per-minute limit. Retry shortly |
| 429 | BROADCAST_LIMIT_REACHED | Broadcast limit reached for this account in the last 24 hours |
| 451 | OPT_OUT_BLOCKED | Recipient has opted out, so the send is legally blocked |
| 451 | NO_CONSENT | No valid CASL consent on file |
| 451 | CONSENT_EXPIRED | Implied consent expired (2-year CASL limit) |
| 451 | DNCL_BLOCKED | Number on the CRTC DNCL with no exemption (coming soon; not currently returned) |
| 501 | DNCL_COMING_SOON | CRTC Do Not Call List checking is not available yet. |
| 501 | TOLLFREE_800_COMING_SOON | 1-800 numbers are not available yet. Choose another toll-free prefix or a local number. |
| 502 | CARRIER_ERROR | The carrier returned an error while sending. |
| 502 | PROVISIONING_FAILED | The carrier could not complete this number purchase. Nothing was charged. |
| 502 | NUMBER_SEARCH_FAILED | Number search is temporarily unavailable. |
| 502 | RELEASE_FAILED | The carrier did not accept the release. The number is still yours; retry shortly |
| 503 | CARRIER_UNAVAILABLE | The carrier could not be reached; nothing was sent or charged. Retry shortly. |
| 503 | CARRIER_TIMEOUT | The carrier did not answer in time, so the message may or may not have been sent. Nothing is charged. Check the message status before sending again: a retry with the same Idempotency-Key returns the failed message instead of sending it again. |
Email error codes
Email calls can also answer with the codes in the general section above, such as VALIDATION_ERROR, INSUFFICIENT_BALANCE and NOT_FOUND.
| HTTP | Code | Meaning |
|---|---|---|
| 403 | EMAIL_DOMAIN_NOT_ALLOWED | The API key is restricted to certain sending domains, and this one isn't in its list. |
| 403 | TEST_KEY_REQUIRED | Only a test key can call this endpoint. |
| 404 | RECEIVED_EMAIL_NOT_FOUND | No received email with that id. |
| 409 | EMAIL_DOMAIN_ALREADY_EXISTS | The domain is already on this account. |
| 409 | EMAIL_DOMAIN_IN_USE | Another account holds this domain. details.retry_after says when an unverified claim lapses; a verified domain stays with its owner. |
| 409 | EMAIL_DOMAIN_HAS_INFLIGHT | The domain still has queued, scheduled or sending email. |
| 409 | EMAIL_IDEMPOTENCY_IN_PROGRESS | A request with this Idempotency-Key is still being processed. Retry the same key shortly rather than using a new one. |
| 409 | EMAIL_TEMPLATE_ALIAS_TAKEN | Another template on this account already uses this alias. |
| 409 | EMAIL_TEMPLATE_IN_USE | A scheduled email uses this template. Cancel it, or wait until it's sent, then delete the template. |
| 409 | RECEIVING_REQUIRES_VERIFIED_DOMAIN | The domain must be verified for sending before receiving can be turned on. |
| 410 | ATTACHMENT_EXPIRED | The attachment or raw message is past its 40 day retention window. |
| 413 | EMAIL_ATTACHMENT_TOO_LARGE | Attachments exceed 25 MB. |
| 422 | EMAIL_INVALID_ADDRESS | An address is not a valid email address. |
| 422 | EMAIL_DOMAIN_NOT_VERIFIED | The from domain is not a verified sending domain on this account. |
| 422 | EMAIL_DOMAIN_INVALID | Not a valid domain name. |
| 422 | EMAIL_DOMAIN_LIMIT_REACHED | This account has reached its sending-domain limit (5 by default; failed domains do not count). Answered to adding a domain, or to verifying a failed domain that would take a slot back. Remove one or ask support to raise it. |
| 422 | EMAIL_TEST_ADDRESS_LIVE_KEY | A live key can't send to an address on test.honkio.ca; those addresses are for test keys only. |
| 422 | EMAIL_HEADER_INVALID | The from address or a header is malformed, or is one honkio sets itself. |
| 422 | EMAIL_ATTACHMENT_FETCH_FAILED | An attachment given by path could not be fetched. details.reason says why: blocked_address, http_status, timeout, too_large or network. |
| 422 | EMAIL_ATTACHMENT_TYPE_BLOCKED | The attachment is an executable or script type (.exe, .js, .bat, .jar and the like) that receiving mail servers reject. details.filename names it. Nothing is charged. |
| 422 | EMAIL_RENDER_FAILED | The content could not be rendered. |
| 422 | EMAIL_BATCH_TOO_LARGE | Too many recipients in one call. |
| 422 | EMAIL_COMMERCIAL_MULTI_RECIPIENT | A commercial email goes to exactly one recipient. Use a batch to reach several. |
| 422 | EMAIL_NOT_SCHEDULED | Only a scheduled email can be moved or cancelled. |
| 422 | EMAIL_TEMPLATE_NOT_PUBLISHED | The template has no published version yet. Publish it before sending with it. |
| 422 | EMAIL_TEMPLATE_VARIABLE_MISSING | A template variable has no value and no fallback. details.keys lists the missing ones. |
| 422 | EMAIL_TEMPLATE_UNDECLARED_VARIABLE | The template body uses a variable it doesn't declare. Add it to variables; details.keys lists them. |
| 422 | EMAIL_TEMPLATE_LIMIT_REACHED | This account has reached the limit of 200 email templates. Delete one to create another. |
| 422 | SIMULATE_RECIPIENT_NOT_OWNED | Every simulated recipient must be your test address or a domain this account owns in test mode. |
| 422 | INBOUND_ADDRESS_DISABLED | Your managed inbound address is switched off. Turn it on before sending to it. |
| 451 | EMAIL_SUPPRESSED | The recipient is on your suppression list. |
| 451 | EMAIL_NO_CONSENT | Commercial email needs CASL consent on file for the recipient. |
| 451 | EMAIL_OPT_OUT_BLOCKED | The recipient unsubscribed from this sender. |
| 503 | EMAIL_ATTACHMENTS_UNCONFIGURED | Attachment storage isn't configured, so emails with attachments can't be sent right now. |
Delivery failure codes
These never come back as an HTTP response: the call already answered 201 before SES tried to send. If delivery still fails, one of these appears as failure_code on the email.failed or email.rejected webhook, and the charge is refunded.
| Code | Meaning |
|---|---|
| SES_THROTTLING | SES throttled the send; the email failed and was refunded; send it again. |
| SES_MESSAGE_REJECTED | SES rejected the message outright, for example malformed content; the email failed and was refunded; fix the content and send it again. |
| SES_DOMAIN_NOT_VERIFIED | SES had not finished verifying the from domain, even though honkio's own check passed; the email failed and was refunded; wait for verification to finish, then send it again. |
| SES_ERROR | An SES error that doesn't fit the other codes; the email failed and was refunded; send it again. |
| EMAIL_ATTACHMENTS_UNAVAILABLE | The stored attachment bytes could not be read at send time; the email failed and was refunded; send it again. |
HonkIO