API conventions
Errors
Every failed request returns the same error object. This page explains its fields and lists every error code with its cause and the fix.
The error object#
The API uses conventional HTTP status codes: 2xx for success, 4xx for a problem with the request and 5xx for a problem on the OmniMessage side. Every non-2xx response has a JSON body with a single error object.
{
"error": {
"type": "invalid_request_error",
"code": "parameter_missing",
"message": "text.body is required.",
"param": "text.body",
"details": [
{
"param": "text.body",
"code": "missing_field",
"message": "Required"
}
],
"request_id": "req_0aB3cD6eF9gH2iJ5kL8m",
"doc_url": "https://omnimessage.co/docs/errors#parameter_missing"
}
}| Field | Always present | Description |
|---|---|---|
type | Yes | The category of the error. One per HTTP status class; see the table below. |
code | Yes | A stable, machine-readable code. Branch on this. |
message | Yes | A human-readable explanation for logs and developers. The wording may change, so do not parse it or show it to end users. |
param | No | The request field the error relates to, as a path such as text.body. |
details | No | One entry per validation failure when several fields are wrong, each with param, code and message. |
request_id | Yes | The ID of the request, also sent in the X-Request-Id header. Include it when you contact support. |
doc_url | Yes | A link to the entry for code on this page. |
Error types#
Handle errors#
- Branch on
typefor the general strategy and oncodefor specific cases. Never branch onmessage. - Treat unknown codes as their
type. New codes can be added to an existing type without a new API version. - 4xx errors, except
429andidempotency_key_in_use, will not succeed if you repeat the same request. Fix the cause first. 429and 5xx errors are transient. Retry with exponential backoff, and reuse theIdempotency-KeyonPOSTrequests so that a retry cannot send twice.- Log
request_idwith every failure.
const response = await fetch('https://api.omnimessage.co/v1/messages', request);
const data = await response.json();
if (!response.ok) {
const { type, code, message, param, request_id } = data.error;
switch (type) {
case 'invalid_request_error': // a bug in the request: log it, do not retry
throw new Error(`Bad request (${code}${param ? ` at ${param}` : ''}): ${message}`);
case 'billing_error': // out of funds: pause sending
await pauseOutboundQueue();
break;
case 'rate_limit_error': // slow down
await sleep(Number(response.headers.get('Retry-After') ?? 1) * 1000);
break;
case 'api_error': // transient: retry with the same Idempotency-Key
await retryLater();
break;
default:
console.error(`OmniMessage error ${code} (${request_id}): ${message}`);
}
}invalid_request_error#
HTTP 400. The request is malformed or a parameter is missing or invalid.
parameter_missing#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- A required field, query parameter or content object is absent.
paramnames it;detailslists every missing field. - Fix
- Add the field. For
POST /v1/messages, make sure the content object sits under the key named bytype. - Retry
- Retry only after fixing the cause.
parameter_invalid#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- A field has the wrong type, format or length, an enum value is unknown, or a limit was exceeded (for example more than 100 messages in a batch).
- Fix
- Correct the value named in
param. Checkdetailsfor the individual failures. - Retry
- Retry only after fixing the cause.
unsupported_message_type#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- The message
typeis not in thecapabilitiesof the channel. - Fix
- Read
capabilitiesfromGET /v1/channels/{id}and send one of the listed types, or use another channel. - Retry
- Retry only after fixing the cause.
invalid_json#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- The body could not be parsed, or
Content-Typeis notapplication/json. - Fix
- Send a UTF-8 JSON body with
Content-Type: application/json. - Retry
- Retry only after fixing the cause.
webhook_endpoint_limit_reached#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- The account already has 500 webhook endpoints in the mode of the API key (live and test are counted separately). Automation tools create one per Zap, workflow or scenario.
- Fix
- Delete endpoints you no longer need (
GET /v1/webhook_endpointsshowssourceandmetadataof each), or subscribe an existing endpoint to more event types. - Retry
- Retry only after fixing the cause.
webhook_url_not_public#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- The webhook URL points at a loopback, private or link-local address (
localhost,127.0.0.1,10.x,192.168.x,*.local,*.internal), or does not usehttps. Deliveries are sent from our servers and cannot reach such a host. - Fix
- Use a public
httpsURL. For a self-hosted tool behind a firewall, expose its webhook path through a tunnel or reverse proxy and set that public address as the tool's webhook URL. - Retry
- Retry only after fixing the cause.
integration_source_limit_reached#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- The account already has 50 integration sources in the mode of the API key.
- Fix
- Delete sources of stores or sites that are no longer connected.
- Retry
- Retry only after fixing the cause.
payload_too_large#
- HTTP status
- 413 Payload too large
- Type
invalid_request_error- Cause
- The body exceeds the limit of the endpoint: 256 KB for automation events and native webhooks, 1 MB elsewhere.
- Fix
- Send less in one request: fewer events per batch, or a smaller
dataobject. - Retry
- Retry only after fixing the cause.
contact_limit_reached#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- The account holds as many contacts as its limit allows, so a new one cannot be created. Updates of existing contacts still work.
- Fix
- Delete contacts you no longer message, or ask support to raise the limit of the account.
- Retry
- Retry only after fixing the cause.
campaign_limit_exceeded#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- The audience of the campaign is larger than the per-campaign limit, or launching it would exceed the number of recipients the account may reach within 24 hours.
- Fix
- Narrow the audience or split it over several campaigns and days. Support can raise the limits of an account.
- Retry
- Retry only after fixing the cause.
consent_required#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- A campaign was launched, or a contact import started, without the declaration that the recipients gave permission to be contacted.
- Fix
- Send
consent_declared: trueonce you have that permission. The declaration is recorded with the campaign or import. - Retry
- Retry only after fixing the cause.
import_file_invalid#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- Console only. An uploaded contact file is empty, too large, not a CSV or .xlsx file, has no header or data rows, has more rows than allowed, or is an archive that unpacks to more data than is accepted.
- Fix
- Upload a .csv or .xlsx file with a header row, within the size and row limits shown in the import dialog.
- Retry
- Retry only after fixing the cause.
token_invalid#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- Console only. An email verification, password reset or invitation link was already used, has expired or is malformed.
- Fix
- Request a new link.
- Retry
- Retry only after fixing the cause.
oauth_pending_expired#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- Console only. A social sign-in that still needed an email address or a password confirmation was not finished within 15 minutes, was already finished, or was started in another browser.
- Fix
- Start the sign-in again from the login or signup page.
- Retry
- Retry only after fixing the cause.
api_key_limit#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- Console only. The account already has 50 API keys that are not revoked.
- Fix
- Revoke a key that is no longer used, then create the new one.
- Retry
- Retry only after fixing the cause.
signature_invalid#
- HTTP status
- 400 Bad request
- Type
invalid_request_error- Cause
- Payment provider callback only. The
Stripe-Signatureheader is missing or does not match the request body. - Fix
- Not returned to API clients. Check the webhook signing secret configured for the payment provider.
- Retry
- Retry only after fixing the cause.
authentication_error#
HTTP 401. The API key is missing or not usable.
webhook_signature_invalid#
- HTTP status
- 401 Unauthorized
- Type
authentication_error- Cause
- Native platform webhooks only (
POST /v1/ingest/{platform}/{source_id}). The signature header (X-WC-Webhook-Signature,X-Shopify-Hmac-Sha256) is missing or was not computed over this body with the signing secret of the source. - Fix
- Paste the signing secret of the source into the platform's webhook settings (WooCommerce), or store the platform's own signing key on the source (Shopify). Rolling the source key issues a new secret.
- Retry
- Retry only after fixing the cause.
api_key_missing#
- HTTP status
- 401 Unauthorized
- Type
authentication_error- Cause
- The
Authorizationheader is absent or does not use theBearerscheme. - Fix
- Add
Authorization: Bearer om_live_...(orom_test_...) to the request. - Retry
- Retry only after fixing the cause.
api_key_invalid#
- HTTP status
- 401 Unauthorized
- Type
authentication_error- Cause
- The key is malformed, truncated or does not exist.
- Fix
- Copy the key again from where you stored it. Keys are shown once at creation; if it is lost, create a new one in the console.
- Retry
- Retry only after fixing the cause.
api_key_revoked#
- HTTP status
- 401 Unauthorized
- Type
authentication_error- Cause
- The key was revoked in the console.
- Fix
- Create a new key and deploy it.
- Retry
- Retry only after fixing the cause.
api_key_expired#
- HTTP status
- 401 Unauthorized
- Type
authentication_error- Cause
- The key was created with an expiry date that has passed.
- Fix
- Create a new key and deploy it.
- Retry
- Retry only after fixing the cause.
session_required#
- HTTP status
- 401 Unauthorized
- Type
authentication_error- Cause
- Console only (session cookie authentication; never returned by
/v1). There is no valid session: the user is signed out or the session expired. - Fix
- Sign in again.
- Retry
- Retry only after fixing the cause.
invalid_credentials#
- HTTP status
- 401 Unauthorized
- Type
authentication_error- Cause
- Console only (never returned by
/v1). The email and password do not match an active user. Also returned with status400when the current password is wrong while changing it. - Fix
- Check the credentials, or reset the password.
- Retry
- Retry only after fixing the cause.
hook_token_invalid#
- HTTP status
- 401 Unauthorized
- Type
authentication_error- Cause
- Delivery layer callback only (never returned by
/v1). The callback did not carry the shared gateway token. - Fix
- Not returned to API clients.
- Retry
- Do not retry.
billing_error#
HTTP 402. The account cannot pay for the message.
insufficient_balance#
- HTTP status
- 402 Payment required
- Type
billing_error- Cause
- No applicable package has credits left and the wallet holds less than the per-message price for the channel type.
- Fix
- Top up the wallet or buy a package in the console, or enable auto-recharge. The message was not created; send it again once funds are available.
- Retry
- Retry only after fixing the cause.
permission_error#
HTTP 403. The key is valid but not allowed to do this.
scope_missing#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- The key lacks the scope the endpoint requires.
- Fix
- Use a key that has the scope, or create one in the console. Scopes of an existing key cannot be read back other than through
GET /v1/me. - Retry
- Retry only after fixing the cause.
ip_not_allowed#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- The key has an IP allowlist and the request came from an address outside it.
- Fix
- Call from an allowed address or update the allowlist of the key in the console.
- Retry
- Retry only after fixing the cause.
account_suspended#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- The account was suspended.
- Fix
- Contact support.
- Retry
- Do not retry.
live_mode_required#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- The operation exists in live mode only and was called with a test key.
POST /v1/channelsis the only such operation: test mode uses the built-in sandbox channels. - Fix
- Call the endpoint with an
om_live_key. In test mode, send on a sandbox channel such asch_test_whatsapp. - Retry
- Retry only after fixing the cause.
test_mode_required#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- The operation exists in test mode only and was called with a live key.
POST /v1/test/inbound_messagesis the only such operation. - Fix
- Call the endpoint with an
om_test_key. Live inbound messages come from your customers through the connected channel. - Retry
- Retry only after fixing the cause.
source_disabled#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- The integration source the request belongs to was disabled in the console. Its source key and its native webhooks are refused until it is enabled again.
- Fix
- Enable the source in the console (Integrations → the source → Settings). A plugin should stop sending and show a reconnect notice.
- Retry
- Retry only after fixing the cause.
csrf_header_missing#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- Console only (never returned by
/v1). A state-changing console request did not carry theX-Requested-With: omni-webheader. - Fix
- Send the header with every
POST,PUT,PATCHandDELETErequest to the console API. - Retry
- Retry only after fixing the cause.
account_required#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- Console only (never returned by
/v1). The signed-in user is not a member of a customer account. - Fix
- Sign in with a user that belongs to an account.
- Retry
- Do not retry.
role_not_allowed#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- Console only (never returned by
/v1). The role of the signed-in member does not allow the action. - Fix
- Ask an owner or admin of the account to perform the action or to change your role.
- Retry
- Do not retry.
admin_required#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- Console only (never returned by
/v1). The endpoint is reserved for OmniMessage staff. - Fix
- Not available to customer accounts.
- Retry
- Do not retry.
impersonation_restricted#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- Console only (never returned by
/v1). The action is not allowed in a support session opened on behalf of the account. - Fix
- Perform the action from the account owner's own session.
- Retry
- Do not retry.
reauthentication_required#
- HTTP status
- 403 Forbidden
- Type
permission_error- Cause
- Console only. Linking or unlinking a sign-in provider, or setting a first password, needs a sign-in or password confirmation from the last 10 minutes.
- Fix
- Confirm the password (
POST /console/auth/reauthenticate) or sign in again with a linked provider, then repeat the request. - Retry
- Retry only after fixing the cause.
not_found_error#
HTTP 404. The object does not exist in this account and mode.
resource_missing#
- HTTP status
- 404 Not found
- Type
not_found_error- Cause
- The ID does not exist, belongs to another account, or belongs to the other mode (a live key cannot see test objects and vice versa).
- Fix
- Check the ID and that the key mode matches the object.
- Retry
- Retry only after fixing the cause.
conflict_error#
HTTP 409. The request conflicts with another request or an existing object.
idempotency_key_in_use#
- HTTP status
- 409 Conflict
- Type
conflict_error- Cause
- A request with the same
Idempotency-Keyis still in flight. - Fix
- Wait briefly and retry with the same key. You then receive the stored response of the first request.
- Retry
- Safe to retry, with backoff.
idempotency_key_reused#
- HTTP status
- 409 Conflict
- Type
conflict_error- Cause
- The
Idempotency-Keywas used in the last 24 hours with a different body. - Fix
- Generate a new key for each distinct operation. Reuse a key only to retry the identical request.
- Retry
- Retry only after fixing the cause.
channel_identifier_taken#
- HTTP status
- 409 Conflict
- Type
conflict_error- Cause
- A channel of the same type with the same
identifieris already connected. - Fix
- Use the existing channel, or delete it before connecting the identifier again.
- Retry
- Retry only after fixing the cause.
email_taken#
- HTTP status
- 409 Conflict
- Type
conflict_error- Cause
- Console only. The email address is already registered.
- Fix
- Sign in, or reset the password of the existing user.
- Retry
- Retry only after fixing the cause.
member_exists#
- HTTP status
- 409 Conflict
- Type
conflict_error- Cause
- Console only. The invited email address already belongs to a member of an account.
- Fix
- Invite a different email address.
- Retry
- Retry only after fixing the cause.
identity_exists#
- HTTP status
- 409 Conflict
- Type
conflict_error- Cause
- Console only. The Google, Apple or Facebook account is already linked to a user, or the user already has an account of that provider linked.
- Fix
- Unlink the provider from the other user first, or sign in with it directly.
- Retry
- Retry only after fixing the cause.
last_sign_in_method#
- HTTP status
- 409 Conflict
- Type
conflict_error- Cause
- Console only. Unlinking the provider would leave the user without a password and without any linked provider.
- Fix
- Set a password or link another provider, then unlink.
- Retry
- Retry only after fixing the cause.
contact_exists#
- HTTP status
- 409 Conflict
- Type
conflict_error- Cause
- Another contact of the account already has the phone number (after normalisation to E.164).
- Fix
- Update the existing contact, or use
POST /v1/contacts/upsert, which creates or updates by phone number. - Retry
- Retry only after fixing the cause.
campaign_state_invalid#
- HTTP status
- 409 Conflict
- Type
conflict_error- Cause
- The action does not apply to the current
statusof the campaign: only a draft can be launched, only a scheduled, queued or sending campaign can be paused, only a paused one can be resumed, and a finished one cannot be cancelled. - Fix
- Retrieve the campaign and act on its current
status. - Retry
- Retry only after fixing the cause.
import_state_invalid#
- HTTP status
- 409 Conflict
- Type
conflict_error- Cause
- Console only. A contact import was started or cancelled in a state that does not allow it.
- Fix
- Reload the import and upload the file again if it needs to be repeated.
- Retry
- Retry only after fixing the cause.
channel_error#
HTTP 422. The request is valid but the channel cannot carry it out.
channel_not_connected#
- HTTP status
- 422 Unprocessable
- Type
channel_error- Cause
- The channel
connection_statusis notconnected, or the channel is suspended. - Fix
- Reconnect the channel in the console. Subscribe to
channel.disconnectedto be told when this happens. - Retry
- Retry only after fixing the cause.
recipient_blocked#
- HTTP status
- 422 Unprocessable
- Type
channel_error- Cause
- The recipient is blocked or has opted out on the channel.
- Fix
- Do not retry. Remove the recipient from your sending list for this channel.
- Retry
- Do not retry.
policy_violation#
- HTTP status
- 422 Unprocessable
- Type
channel_error- Cause
- The channel policy does not allow the message, for example free-form WhatsApp content outside the 24-hour customer service window.
- Fix
- Send content the channel allows in this situation, such as an approved WhatsApp template.
- Retry
- Retry only after fixing the cause.
upstream_rejected#
- HTTP status
- 422 Unprocessable
- Type
channel_error- Cause
- The channel provider refused the request, for example invalid credentials when connecting a channel or content it does not accept.
- Fix
- Read
messagefor the provider reason, correct the request and send it again. - Retry
- Retry only after fixing the cause.
template_not_approved#
- HTTP status
- 422 Unprocessable
- Type
channel_error- Cause
- A live WhatsApp campaign does not use a message template, or the template it names does not exist on the channel or is not approved by WhatsApp.
- Fix
- List the templates with
GET /v1/channels/{id}/templatesand use one whosestatusisAPPROVED, with one parameter per variable of its body. - Retry
- Retry only after fixing the cause.
rate_limit_error#
HTTP 429. Too many requests in a short time.
rate_limit_exceeded#
- HTTP status
- 429 Too many requests
- Type
rate_limit_error- Cause
- The API key sent more requests than its per-second limit allows.
- Fix
- Wait for the number of seconds in
Retry-After, then retry. Smooth bursts with a client-side queue. - Retry
- Safe to retry, with backoff.
account_locked#
- HTTP status
- 429 Too many requests
- Type
rate_limit_error- Cause
- Console only. Five failed sign-in attempts lock sign-in for the email address for 15 minutes.
- Fix
- Wait for the number of seconds in
Retry-After, or reset the password. - Retry
- Safe to retry, with backoff.
api_error#
HTTP 500 or 503. Something went wrong on the OmniMessage side.
internal_error#
- HTTP status
- 500 Server error
- Type
api_error- Cause
- Something failed on our side.
- Fix
- Retry with exponential backoff and the same
Idempotency-Key. If it persists, contact support with therequest_id. - Retry
- Safe to retry, with backoff.
upstream_unavailable#
- HTTP status
- 503 Service unavailable
- Type
api_error- Cause
- The delivery layer could not be reached. The message was not accepted and any charge was reversed.
- Fix
- Retry with exponential backoff and the same
Idempotency-Key. - Retry
- Safe to retry, with backoff.