Error Code Reference
Every Orbit API error response carries a machine-readablecode,
human-readable message, HTTP status, and contextual details.
This page is generated from the canonical ERROR_CODES registry in
packages/shared/src/errors.ts and lists every code in that
registry — 732 in total — grouped by domain. Every code
listed here is one the SDK and meta.docs_url link against. Some
route handlers also return operational codes that are not yet in the
registry and so are absent from the table below, so treat the code
on any server response as authoritative even when it is not listed
here.
HTTP status codes shown reflect the canonical status documented in the
source. Some codes are reused at multiple call sites with different
statuses (e.g.
VALIDATION_ERROR may surface as 400 or 422); the
table picks the dominant value. Treat the status field on the
response envelope as authoritative for any given response.Error Response Format
{
"error": {
"code": "INVALID_PHONE_NUMBER",
"message": "The 'to' field must be a valid E.164 phone number",
"status": 422,
"details": {
"field": "to",
"value": "+1234",
"expected": "E.164 format e.g. +14155552671"
}
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-03-08T00:00:00Z",
"docs_url": "https://docs.orbit.devotel.io/errors/INVALID_PHONE_NUMBER"
}
}
meta.docs_url on an error envelope is a bare
/errors/<CODE> path, and docs redirects every /errors/* URL to
this page with an HTTP 307 — so follow the link when you can read the
page, and when you cannot, the anchor
/reference/error-codes#CODE_NAME (case-insensitive)
is the stable per-code target no /errors/* URL will ever claim.
Worked example — a 422 envelope end to end
curl -X POST https://api.orbit.devotel.io/api/v1/messages/sms \
-H "X-API-Key: dv_live_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"to": "1234", "body": "Hello"}'
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid SMS payload",
"status": 422,
"details": {
"issues": [
{ "field": "to", "message": "Must be a valid E.164 phone number" }
]
}
},
"meta": {
"request_id": "req_1234",
"timestamp": "2026-05-10T12:00:00Z",
"docs_url": "https://docs.orbit.devotel.io/errors/VALIDATION_ERROR"
}
}
From a code to a runbook
When an error carries acode that has no dedicated runbook page yet,
route it by CLASS — do not read the description column as the only hint.
| Code class | Example codes | Retry-safety | Where to check next |
|---|---|---|---|
| Tenant-owned pre-send reject (403/422 gate) | DNC_SYNC_NOT_ENABLED, FRAUD_SHIELD_BLOCKED, SENDER_ID_NOT_REGISTERED, HIPAA_BAA_REQUIRED | Retry only after the payload or tenant config is fixed — an immediate repeat fails the same way. | Send-gate and compliance runbooks |
| Transient 429 / 5xx | RATE_LIMITED, SERVICE_UNAVAILABLE, INTERNAL_ERROR | Retriable — honor details.retry_after seconds when present, otherwise exponential backoff. | Rate limits and cool-downs |
| Deterministic 422 validation | INVALID_PHONE_NUMBER, MISSING_REQUIRED_FIELD, NO_VARIANT_FOR_CHANNEL | Never retry — the request will fail identically until the payload changes. | The envelope’s details object tells you which field and what it expected; the decode-and-fix walkthrough is the validation gates runbook. |
| Unknown or unclassified code | Any code absent from the table above | Treat as a transient 5xx until told otherwise, and open a ticket with meta.request_id. | Error envelope structure; if it is a send gate post-classification, jump to the Troubleshooting hub. |
docs_url anchor on this page is the fallthrough, not the only
destination.
Authentication
| Code | HTTP | Description |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid API key / bearer token (401). |
INVALID_API_KEY | 401 | The API key isn’t recognized—check you’re using the full dv_sk key (401). |
EXPIRED_TOKEN | 401 | An auth or reset token has expired—re-authenticate or request a fresh one (401). |
INSUFFICIENT_PERMISSIONS | 403 | Authenticated, but the API key / role lacks the permission for this route (403). |
IP_NOT_ALLOWED | 403 | The caller’s IP is blocked by an org API-key allowlist or platform denylist (403). |
NOT_OWNING_AGENT | 404 | 404 — POST /voice/queues/:queueId/calls/:callId/disposition refused to record a disposition because the authenticated agent is not the assigned_agent_user_id on the queue entry. |
WRONG_KEY_MODE | 403 | Audit #SANDBOX-1 — returned when an API key’s mode (sandbox vs. live, derived from the dv_test_sk_* / dv_live_sk_* prefix and the api_keys.mode column) does not match the resolved organization’s is_sandbox flag. |
SAML_USER_NOT_IN_ORG | varies | Returned by the SAML ACS when an IdP assertion succeeds cryptographically but the asserted email either belongs to a different Devotel organisation OR has no invitation / enforcement on the callback org. |
SCIM_ROLE_CEILING_EXCEEDED | varies | Returned by the SCIM PATCH /Groups endpoint when the requested target role exceeds the org’s configured max_assignable_role ceiling (default ‘admin’). |
CLIENT_DISCONNECTED | 499 | 499 — the caller closed the connection before the response could be delivered (browser tab closed, mobile network handoff, proxy/LB idle-timeout drop). |
WEBHOOK_BODY_MISSING | varies | Returned when a Clerk webhook arrives but the content-type parser that captures the raw request bytes for svix signature verification didn’t run (e.g. non-JSON content-type). |
INVALID_SIGNATURE | 401 | HTTP 401 — an inbound webhook receiver rejected a request because the provider signature (HMAC / svix / X-Hub-Signature-256 / Twilio X-Twilio-Signature, etc.) failed verification, or the timing-safe comparison against the configured sh… |
STALE_WEBHOOK | 401 | HTTP 401 — an inbound webhook/DLR receiver rejected a request because the event timestamp is missing or falls outside the allowed replay window (a clock-skew / replay-attack guard). |
CANNOT_REVOKE_CURRENT_SESSION | 400 | HTTP 400 — the session-revoke endpoint (DELETE /settings/security/sessions/:sessionId) refused to revoke the caller’s own currently-signed-in session; end the current session through the sign-out flow instead. |
Validation
| Code | HTTP | Description |
|---|---|---|
VALIDATION_ERROR | varies | The request body or query failed schema validation; details.issues lists the offending fields (400 or 422). |
DB_ERROR | 500 | 500 — generic database operation failure surfaced to clients by the inbox conversation-tags + SLA controllers (e.g. “Failed to create conversation tag definition”). |
NO_VARIANT_FOR_CHANNEL | 422 | 422 — a content template (multi-channel message template) has no variant for the requested target channel and no fallback in fallback_chain resolved to a channel that does. |
WEAK_IDENTITY_MATCH_REQUIRES_CONFIRMATION | 422 | 422 — POST /conversations/:id/merge refused a merge whose only cross-row identity overlap is phone OR email while the rows ALSO carry contradicting contact_id values (different ids, or one side has an id and the other doesn’t). |
INVALID_PHONE_NUMBER | 422 | A phone field isn’t valid E.164 (422). |
INVALID_FROM_NUMBER | 422 | The from_number field is missing or malformed (422). |
INVALID_EMAIL | 422 | The email recipient address is malformed (422). |
DISPOSABLE_EMAIL_NOT_ALLOWED | 422 | 422 — a team invitation (POST /settings/team/invite, its bulk sibling POST /settings/team/invites/bulk) was addressed to a known disposable / throwaway mail provider (temp-mail, mailinator, sharklasers, …). |
INVALID_TEMPLATE | 422 | The rendered message template isn’t valid (e.g. unresolved variables or bad structure) (422). |
MISSING_REQUIRED_FIELD | varies | A required field is absent from the request (400 or 422). |
MMS_NANP_ONLY | varies | MMS submitted to a non-NANP destination. |
INVALID_PHONE_NUMBER, INVALID_FROM_NUMBER, INVALID_EMAIL, and MISSING_REQUIRED_FIELD share one decode-and-fix walkthrough on the validation gates runbook: read error.details for the field-level hint, pre-flight the value with GET /api/v1/numbers/lookup/{phoneNumber} or an E.164 formatter, correct the named field, and re-send once.
| ATTACHMENT_TOO_LARGE | 422 | 422 — POST /inbox/tickets/:id/attachments refused to register a ticket attachment because the declared size_bytes exceeds the per-attachment ceiling (TICKET_ATTACHMENT_MAX_BYTES, 25 MiB). |
| INVALID_CURSOR | 400 | HTTP 400 — a cursor-paginated list endpoint rejected the cursor query parameter because it is malformed, truncated, tampered, or no longer resolves to a row in the current result window. |
| TURNSTILE_REQUIRED | 422 | 422 — a public, unauthenticated anti-bot endpoint rejected a submission because the Cloudflare Turnstile token was missing, malformed, or failed siteverify (fail-closed when the Turnstile secret is configured). |
Resources
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | The requested resource doesn’t exist (404). |
DOMAIN_NOT_FOUND | 404 | 404 — GET /public/resolve-domain (the unauthenticated host→tenant lookup the Next.js edge middleware calls on every request whose Host isn’t the platform’s primary domain) found no ACTIVE custom-domain row matching the given host. |
COMMUNITY_NOT_FOUND | 404 | 404 — the unauthenticated public community surface (GET|POST /public/community/*, ?org=<slug>) resolved to no ACTIVE, community-enabled tenant for the given org slug (unknown org OR the community feature is disabled — both collapse t… |
IDEAS_NOT_FOUND | 404 | 404 — the unauthenticated public idea-board surface (GET|POST /public/ideas/*, ?org=<slug>) resolved to no ACTIVE, ideas-enabled tenant for the given org slug (unknown org OR the idea board is disabled — both collapse to the identica… |
LINK_NOT_FOUND | 404 | 404 — POST /public/lp/:code/convert (the unauthenticated public landing-page conversion endpoint short links redirect to) resolved no PUBLISHED landing page for the given short code (unknown, unpublished, or expired code — all collapse… |
ORG_NOT_FOUND | 404 | 404 — the unauthenticated public help-center surface (GET|POST /public/help/*, ?org=<slug>) resolved no ACTIVE organization for the given org slug. |
ARTICLE_NOT_FOUND | 404 | 404 — the public help-center article routes (GET /public/help/articles/:slug and POST /public/help/feedback) resolved no PUBLISHED, public article for the given slug / document id (unknown, unpublished, or non-public article — all co… |
TICKET_NOT_FOUND | 404 | 404 — the public help-center ticket-status portal (GET /public/help/tickets/:id) could not authorise + resolve the requested ticket. |
CALLBACK_NOT_FOUND | 404 | 404 — the unauthenticated public callback self-service surface’s JSON routes (GET /public/callbacks/:token/status, POST /public/callbacks/:token/cancel, POST /public/callbacks/:token/reschedule) resolved no callback for the signed… |
ALREADY_EXISTS | 409 | A record with this identifier already exists—use PATCH, or pick a unique value (409). |
CONFLICT | 409 | The request conflicts with current state (e.g. duplicate value, optimistic-lock version mismatch) (409). |
DUPLICATE_SENDER_ID | 409 | 409 — POST /settings/sender-ids refused to register a sender ID because an entry with the same value already exists on this workspace for the same channel. |
CONTACT_PHONE_ALREADY_EXISTS | 409 | 409 — POST /contacts or PATCH /contacts/:id collided with the tenant’s partial UNIQUE index contacts_phone_live_uq (predicate: deleted_at IS NULL AND phone IS NOT NULL). |
CONTACT_EMAIL_ALREADY_EXISTS | 409 | 409 — same shape as CONTACT_PHONE_ALREADY_EXISTS but for the partial UNIQUE index contacts_email_live_uq. |
CONTACT_WHATSAPP_BSUID_ALREADY_EXISTS | 409 | 409 — partial UNIQUE index idx_contacts_whatsapp_bsuid collision. |
CONTACT_ARCHIVED_REQUIRES_UNARCHIVE | 409 | 409 — PUT /contacts/:id attempted to move an archived contact to a non-archived lifecycle_stage without the explicit unarchive: true companion flag. |
TEMPLATE_NAME_ALREADY_EXISTS | 409 | 409 — POST /messages/templates collided with the tenant’s uq_templates_channel_name unique index (channel, name). |
CONTACT_NOT_FOUND_FOR_IDENTIFIER | 404 | 404 — POST /contacts/lists/:id/members identifier resolver could not map the operator-supplied phone / email / contact id / name to any existing contact in the tenant. |
GONE | 410 | HTTP 410 Gone — resource existed but is permanently unavailable (revoked / expired / single-use exhausted). |
Rate Limiting
| Code | HTTP | Description |
|---|---|---|
RATE_LIMIT_EXCEEDED | 429 | Per-recipient, per-tenant, and per-resource frequency-cap HTTP 429, raised by the messaging, verify, DSAR, and number-pool limiters with a retry_after value in seconds. |
QUOTA_EXCEEDED | varies | The API key or tenant went over its configured quota (e.g. numbers, SIMs, toll-free resporg entries) (429 or 422). |
DAILY_CAP_EXCEEDED | 429 | Audit #WARMING-1 — per-DID 10DLC daily-cap reached on the pre-send gate (apps/api/src/lib/sms-warming-cap.ts). |
WARMING_QUOTA_EXCEEDED | varies | NaaS number-warming progression (2026-05-28) — newly-acquired 10DLC numbers ramp gradually for carrier reputation. |
NUMBER_MPS_EXCEEDED | 429 | NaaS per-DID messaging throughput cap (2026-06-09) — operator-set hard per-second send ceiling on a single number, enforced by apps/api/src/lib/per-number-mps-cap.ts. |
Messaging
| Code | HTTP | Description |
|---|---|---|
MESSAGE_SEND_FAILED | 502 | The provider rejected the message send (502). |
EMAIL_SEND_FAILED | 500 | 500 — POST /messages email send: a single recipient’s send failed with a non-DevotelError rejection (e.g. a Resend 5xx, fetch ECONNRESET, or a Drizzle/postgres fault inside the send pipeline). |
EMAIL_SEND_FAILED_ALL | varies | POST /messages email fan-out: EVERY recipient failed. |
MESSAGE_NOT_EDITABLE | 409 | 409 — PATCH /messages/:id (Twilio Message.update parity) refused to edit a scheduled message that is no longer in scheduled state. |
MESSAGE_NOT_CANCELLABLE | 409 | 409 — DELETE /messages/:id refused to cancel a scheduled message that is no longer in scheduled state. |
CONVERSATION_NOT_FOUND | 404 | 404 — POST /messages was given an optional conversation_id to thread a reply, but the id does not reference a conversation on your account. |
CONVERSATION_MISMATCH | 422 | 422 — POST /messages conversation_id resolved to a real conversation, but it does not belong to this recipient and channel. |
NOTIFY_NOT_FOUND | 404 | 404 — GET /notify/:notifyId (the unified cross-channel cascade delivery receipt) found no messages row stamped with this notify_id in the caller’s tenant. |
CHANNEL_UNAVAILABLE | varies | The requested channel (sms / whatsapp / …) isn’t reachable for this tenant (422 or 503). Triage path: CHANNEL_UNAVAILABLE and fallback chain exhaustion. |
CHANNEL_NOT_CONFIGURED | 422 | This channel isn’t configured for the tenant—complete channel setup first (422). |
CHANNEL_COMING_SOON | 422 | The requested channel isn’t generally available yet (422). |
CHANNEL_NOT_REPLYABLE | 422 | SCAN-INBOX-018 (2026-05-23) — caller selected a reply channel that is an inbox-only triage label (agent, video) rather than an outbound provider. |
MISSING_IDENTITY | varies | SCAN-INBOX-018 (2026-05-23) — caller asked to reply on a real channel but the conversation row carries no usable identity for that channel (e.g. web_chat thread with no metadata.visitor_id; SMS thread with no contact_phone). |
SENDER_ID_NOT_REGISTERED | 422 | The SMS sender ID isn’t registered in the destination country—register it before sending (422). |
SENDER_ID_NOT_APPROVED | 422 | The SMS sender ID is registered but not approved yet (422). |
NO_SENDER_CONFIGURED | 422 | #R3-ONB-T1-FIRSTSEND (2026-05-24) — first-send opaque error fix. |
SENDER_REQUIRED | 422 | 422 — a direct API / SDK send omitted every sender selector (from, sender_pool_id, messaging_service_id, from_extension). |
CHANNEL_REQUIRED | 422 | A send did not name its CHANNEL. |
SENDER_INVALID_FOR_DESTINATION | 422 | Sender ID violates the destination’s per-country format / prefix / length rules (packages/messaging/src/sender-id-rules.ts) — since 2026-08-16 only enforced in strict sender-ID mode, permissive by default; status 422 when it fires. |
SENDER_ID_CHECK_UNAVAILABLE | 503 | Wave-W1 CPA-001 — Alpha sender approval gate could not reach Redis or the DB to verify whether the supplied alphanumeric sender ID is approved for this tenant. |
SENDER_NOT_OWNED | 422 | Wave-W1 SCAN-TI-005 — numeric E.164 sender that is NOT in the calling tenant’s phone_numbers table BUT does exist in public.platform_numbers assigned to a different organization. |
SENDER_OWNERSHIP_CHECK_UNAVAILABLE | 503 | TI-2026-002 — companion to SENDER_NOT_OWNED. |
SENDER_POOL_RESOLUTION_FAILED | varies | Sender-pool resolution failed at send time. |
SENDER_POOL_NOT_FOUND | varies | The sender_pool_id on a send refers to a row that doesn’t exist in this tenant. |
SENDER_POOL_EMPTY | varies | The pool resolved fine but its sender_dids array is empty — the dashboard should prompt for at least one DID. |
FALLBACK_CARRIER_NOT_FOUND | varies | retry_policy.fallback_carriers referenced a carrier id (or slug) that doesn’t exist in this tenant’s smpp_carriers table. |
TEMPLATE_NOT_APPROVED | 422 | The message template is pending provider approval (WhatsApp / RCS) (422). |
TEMPLATE_SEND_FAILED | 502 | Sending the template message failed upstream (502). |
TEMPLATE_FETCH_FAILED | 502 | Fetching templates from the provider failed upstream (502). |
TEMPLATE_CREATE_FAILED | 502 | Creating the template on the provider side failed (502). |
TEMPLATE_PARAMS_MISMATCH | varies | Legacy — superseded by WHATSAPP_TEMPLATE_VAR_COUNT_MISMATCH. |
TEMPLATE_NOT_LOCALIZABLE | 422 | 422 — POST /messages/templates/:id/localize refused to auto-localize a template that has no translatable body text, because there is nothing for the model to translate into the target locales. |
WHATSAPP_TEMPLATE_VAR_COUNT_MISMATCH | 400 | Pre-flight check for WhatsApp template sends — caller supplied a template_params count that doesn’t match the distinct {{N}} positional placeholders declared across the template’s header + body components, OR supplied empty/whitespac… |
SANDBOX_MAGIC_NUMBER_BLOCKED | 422 | Pre-send guard — recipient is a vendor-reserved sandbox magic number (Twilio +1 (500) 555-000X range). |
SMS_BLOCKED_DESTINATION | varies | SMS fraud guard: destination prefix on the platform blocklist (premium / pumped routes). |
SMS_RATE_LIMITED | varies | SMS fraud guard: tenant exceeded per-minute send cap. |
SMS_DAILY_SPEND_CAP | varies | SMS fraud guard: tenant exceeded daily spend cap (estimate-based). |
CHANNEL_BLOCKED_DESTINATION | varies | Cross-channel fraud guard codes (audit #MSG-003, 2026-05-11). |
CHANNEL_RATE_LIMITED | 429 | A per-channel sending rate limit tripped—slow down or raise the cap (429). |
CHANNEL_DAILY_SPEND_CAP | 422 | The per-channel daily spend cap is reached—wait for the daily reset or raise the cap (422). |
WHATSAPP_TIER_LIMIT_EXCEEDED | 429 | WhatsApp tier-aware backpressure (CPaaS gap closure, 2026-05-30). |
NOT_SMS_CAPABLE | varies | Reserved messaging-capability error code. |
MEDIA_UPLOAD_FAILED | 502 | Uploading media to the provider (e.g. WhatsApp) failed (502). |
INVALID_RECIPIENT | 422 | The recipient identifier isn’t valid for this channel (422). |
FRAUD_BLOCKED | 422 | The fraud-shield pre-send gate blocked this send (e.g. high-risk destination or pattern) (422). |
FRAUD_SHIELD_BLOCKED | 403 | 403 — the SMS send was rejected by the tenant’s Fraud Shield policy running in enforcement mode block (configured at /settings/compliance/fraud-shield). |
WHATSAPP_SESSION_EXPIRED | 422 | The recipient’s WhatsApp session window has expired—re-authenticate or use a template (422). |
MESSAGING_WINDOW_CLOSED | varies | Meta’s 24-hour messaging window has elapsed for this Messenger or Instagram conversation and the caller did not supply a messaging tag. |
RCS_NOT_SUPPORTED | 422 | RCS isn’t supported for this recipient or tenant (422). |
PENDING_ACCEPTANCE | 425 | Returned by /rcs/test-account/verify-otp while waiting for the user to tap “Make me tester” on the Dotgo RBM tester invite they received in Google Messages. |
INVITE_DECLINED | varies | The user tapped “Decline” on the Dotgo RBM tester invite. |
WHATSAPP_NOT_CONNECTED | varies | Caller tried to use a WhatsApp feature (template create, send, etc.) but the org has no connected WABA. |
WHATSAPP_CONNECTION_INVALID | varies | The org has a WABA row but the encrypted access_token failed to decrypt (key rotation, manual DB edit, encryption-version drift). |
WHATSAPP_BYO_INCOMPLETE | varies | TEST-USER K-004 (2026-05-26) — BYO WhatsApp half-config billing-leak guard. |
WHATSAPP_PAY_NOT_ENABLED | varies | WhatsApp Pay is not enabled on the tenant’s WABA. |
TEMPLATE_REJECTED | varies | Meta rejected a template submission for content reasons. |
WHATSAPP_CALL_PERMISSION_FAILED | varies | Meta rejected a WhatsApp Business Calling permission request — the recipient hasn’t granted call permission inside WhatsApp, or the conversation window is closed. |
WHATSAPP_OUTSIDE_24H_WINDOW | 422 | Meta’s 24-hour customer-service-window gate. |
WHATSAPP_CALLING_BLOCKED_COUNTRY | 422 | The (E.164 country code of the) phone number is on the WA-Calling-blocked country list (US/CA/EG/VN/NG as of May 2026 per Meta’s WhatsApp Business Calling rollout). |
WHATSAPP_CALLING_PROVISION_FAILED | varies | Generic provisioning failure when toggling calling on/off via Meta. |
WHATSAPP_CALLING_PERMISSION_MISSING | varies | The recipient contact has not granted business-calling permission (or it has expired / been revoked). |
WHATSAPP_CALLING_NO_PERMISSION | varies | Phase-2 alias of WHATSAPP_CALLING_PERMISSION_MISSING surfaced specifically by the outbound POST /whatsapp/calling/calls route (initiateCall). |
WHATSAPP_CALLING_INSUFFICIENT_BALANCE | 402 | 402 (or 503) — WA Calling wallet pre-flight on POST /whatsapp/calling/calls: 402 when the wallet can’t cover the worst-case (max-call-minutes × per-minute rate) cost, 503 when the balance read itself fails (fail-closed, retryable). |
WHATSAPP_CALLING_DAILY_CAP_EXCEEDED | 429 | Outbound or inbound WA Calling event was admitted past the country gate but the per-tenant daily cost cap (configured in organizations.settings.whatsapp_calling_daily_cap_cents) is already exhausted. |
WHATSAPP_NO_CALLING_PHONE | 422 | Shared-WABA calling resolver fell through with no callable phone (audit #WABA-SHARED-CALLING-1, 2026-05-11). |
WHATSAPP_CALLING_MEDIA_NOT_PROVISIONED | 503 | Outbound WA Calling refused because the platform’s WhatsApp media gateway is not provisioned yet. |
WHATSAPP_SHARED_CALLING_QUALITY_PAUSED | 503 | Devotel’s shared calling phone has a quality_rating=RED per Meta Graph (audit #WABA-SHARED-CALLING-1). |
VERIFIED_PHONE_OVERWRITE_REQUIRES_CONFIRMATION | 409 | Audit #WABA-TEST-EDITABLE-1 — caller attempted to send a test-account OTP to a phone different from the org’s currently-verified number WITHOUT passing allow_overwrite=true. |
WHATSAPP_CALL_ACTION_FAILED | varies | Meta rejected a call-control action (pre_accept / accept / reject / terminate). |
WHATSAPP_CALL_INITIATE_FAILED | varies | Outbound WhatsApp call initiate failed (POST //calls). |
WHATSAPP_BLOCK_OPERATION_FAILED | varies | Block-list management failed (POST/DELETE/GET //block_users). |
WHATSAPP_BLOCK_LIST_FAILED | 502 | Failed to update the Meta-side WhatsApp block list (502). |
WHATSAPP_PROFILE_GET_FAILED | varies | Business profile read / update via /whatsapp_business_profile. |
WHATSAPP_PROFILE_UPDATE_FAILED | 502 | Failed to update the WhatsApp Business profile (502). |
WHATSAPP_CONVERSATIONAL_AUTOMATION_GET_FAILED | varies | Conversational automation (ice breakers / commands / welcome message) read + update via /conversational_automation. |
WHATSAPP_CONVERSATIONAL_AUTOMATION_UPDATE_FAILED | 502 | Failed to update the WhatsApp conversational-automation setting (502). |
WHATSAPP_TEMPLATE_EDIT_FAILED | varies | Template edit (POST /) and delete (DELETE //message_templates). |
WHATSAPP_TEMPLATE_DELETE_FAILED | 502 | Failed to delete a WhatsApp message template on Meta (502). |
WHATSAPP_REQUEST_CODE_FAILED | varies | Display-name change verification (request_code / verify_code). |
WHATSAPP_VERIFY_CODE_FAILED | 422 | Verifying the WhatsApp number’s OTP code failed (422). |
VERIFY_RECIPIENT_RATE_LIMITED | 429 | Per-recipient verify OTP rate limit hit (anti-SMS-pumping / anti-harassment gate in verify.service.sendVerification). |
VERIFY_RESEND_COOLDOWN | 429 | Per-recipient verify OTP resend cooldown hit. |
WHATSAPP_CATALOG_API_FAILED | varies | Catalog / product CRUD against //owned_product_catalogs. |
CATALOG_ERROR | 500 | 500 — the WhatsApp commerce catalog-list surface (GET /whatsapp/catalogs and GET /whatsapp/catalog) hit an unexpected server-side failure while fetching the tenant’s Meta product catalog. |
CATALOG_SEND_ERROR | 500 | 500 — a WhatsApp commerce product-message send hit an unexpected server-side failure while dispatching a catalog or single/multi product interactive to Meta. |
WHATSAPP_FLOW_SEND_FAILED | varies | Send-flow as interactive message — POST //messages type=flow. |
WHATSAPP_INTERACTIVE_SEND_FAILED | varies | Generic interactive (reply-button / list) send — POST //messages type=interactive. |
WHATSAPP_LOCATION_REQUEST_SEND_FAILED | varies | location_request_message send — POST //messages type=interactive. |
WHATSAPP_ADDRESS_REQUEST_SEND_FAILED | varies | address_message send — POST //messages type=interactive. |
WHATSAPP_ANALYTICS_API_FAILED | varies | //pricing_analytics or /template_analytics fetch. |
WHATSAPP_UPLOAD_API_FAILED | varies | Resumable upload session create / append chunk / finalize. |
WHATSAPP_TEMPLATE_PARAM_MISSING | varies | Meta 131008 — required template parameter missing (commonly AUTH OTP button). |
WHATSAPP_RE_ENGAGEMENT_WINDOW_CLOSED | varies | Meta 131026 — 24h customer-care window expired; only templates allowed. |
WHATSAPP_RE_ENGAGEMENT_REQUIRED | varies | Meta 131047 — re-engagement requires a template since contact’s last message >24h ago. |
WHATSAPP_UNSUPPORTED_MESSAGE_TYPE | varies | Meta 131051 — message type unsupported on recipient device/region. |
WHATSAPP_TEMPLATE_NOT_FOUND | varies | Meta 132001 — template name does not exist in any of the requested languages. |
WHATSAPP_TEMPLATE_PARAM_FORMAT | varies | Meta 132012 — parameter format mismatch (named vs positional, type, length). |
WHATSAPP_TEMPLATE_PAUSED | varies | Meta 132016 — template paused due to low quality / high block rate. |
WHATSAPP_TEMPLATE_DISABLED | varies | Meta 132068/132069 — template disabled by Meta after policy violation. |
WHATSAPP_ACCOUNT_NOT_REGISTERED | varies | Meta 133010 — account hasn’t completed registration; sends blocked. |
WHATSAPP_BILLING_ISSUE | varies | Meta 131042 — WABA payment method failing on Meta’s side; sends blocked until the operator updates billing in Meta Business Manager. |
WHATSAPP_INVALID_PARAMETER | varies | Meta 100 — invalid parameter or deprecated field. |
WHATSAPP_TOKEN_EXPIRED | varies | Meta 190 — access token expired / revoked / scopes removed. |
WHATSAPP_PLATFORM_TRANSIENT_FAULT | varies | Meta 131000 / 141000 — Meta’s generic “Something went wrong” platform- side fault (“retry; open a support ticket if it persists”). |
WHATSAPP_POLICY_BLOCK | varies | Meta 368/470 — temporary policy block on account or recipient. |
WHATSAPP_RECIPIENT_INVALID | varies | Meta 1304/2388042 — recipient phone number invalid for this region. |
WHATSAPP_SERVICE_UNAVAILABLE | varies | Meta 131016 — Cloud API service unavailable. |
WHATSAPP_ACCOUNT_LOCKED | varies | Meta 131031 — WABA locked by Meta after sustained policy issue. |
WHATSAPP_SENDER_NOT_APPROVED | varies | Meta 131045 — sender not approved for marketing templates yet. |
TEMPLATE_SYNC_FAILED | 400 | Devotel — POST /whatsapp/templates/sync failed against Meta Graph (OAuth/token/IP-allowlist/WABA-state). |
WHATSAPP_US_MARKETING_PAUSED | 422 | Devotel preflight — marketing templates to US (+1, non-Canada NANP) recipients are paused by Meta since April 2025. |
WHATSAPP_MARKETING_FREQUENCY_CAPPED | varies | Meta 131049 — a MARKETING template was not delivered to this recipient “to maintain healthy ecosystem engagement” (Meta’s per-recipient marketing-message frequency cap). |
WHATSAPP_USER_OPTED_OUT | varies | Meta 131050 — recipient opted out of marketing on WhatsApp. |
WHATSAPP_MEDIA_DOWNLOAD_FAILED | varies | Meta 131052 — media at the supplied URL couldn’t be downloaded. |
WHATSAPP_MEDIA_UPLOAD_FAILED | varies | Meta 131053 — media upload to WhatsApp failed (size / MIME). |
WHATSAPP_ACCOUNT_QUALITY_LOW | varies | Meta 133000 — overall account quality dropped; sends still allowed but Meta will reduce the messaging tier if it persists. |
WHATSAPP_RATE_LIMITED | varies | Meta 133004/133015 — short rate-limit / cooldown asked by Meta. |
WHATSAPP_2FA_PIN_REQUIRED | varies | Meta 133005-133009 — 2-step verification PIN required / mismatch / cooldown / wrong-attempt lock / wrong length. |
WHATSAPP_TOKEN_EXCHANGE_FAILED | varies | Auth code expired / single-use / wrong redirect — re-run signup. |
WHATSAPP_ACCESS_TOKEN_EXPIRED | varies | Stored long-lived token expired (60d max) — reconnect to refresh. |
WHATSAPP_NO_WABA | varies | No WABA on the user’s Meta login. |
WHATSAPP_NO_PHONE_NUMBER | varies | WABA exists but no phone number is registered on it. |
WHATSAPP_INVALID_SELECTION | varies | Selected (waba_id, phone_number_id) pair isn’t on the user’s token. |
WHATSAPP_PHONE_NOT_OWNED | varies | Selected phone number isn’t part of any WABA the user owns. |
WHATSAPP_BUSINESS_STATUS_PENDING | varies | Business is still under Meta verification review. |
WHATSAPP_BUSINESS_VERIFICATION_REQUIRED | varies | Business hasn’t completed verification at all. |
WHATSAPP_PHONE_VERIFY_REQUIRED | varies | Phone-number SMS/voice OTP not yet completed in WhatsApp Manager. |
WHATSAPP_WABA_ALREADY_LINKED | varies | WABA already linked to another Orbit organization. |
WHATSAPP_META_APP_DISABLED | varies | Devotel’s Meta app is temporarily disabled. |
WHATSAPP_REGION_UNSUPPORTED | varies | Region not in Meta’s supported country list for WhatsApp Business API. |
WHATSAPP_CONNECTION_NOT_FOUND | varies | Existing connection not found on the org (reconnect / rename / default). |
WHATSAPP_TOKEN_EXCHANGE_FAILED through WHATSAPP_CONNECTION_NOT_FOUND (token exchange, selection, ownership, business-verification, and the platform-side Meta app flag) shares one recovery walkthrough on WhatsApp re-authentication and connection recovery.
Telegram
| Code | HTTP | Description |
|---|---|---|
TELEGRAM_PROVIDER_ERROR | varies | Telegram Bot API returned a non-2xx ({ok:false, description}) or a network failure on getMe/setWebhook/sendMessage. |
TELEGRAM_BOT_ALREADY_CONNECTED | varies | Org already has a BYO Telegram bot connected; reconnect by disconnecting first. |
TELEGRAM_INVALID_BOT_TOKEN | varies | BotFather token failed regex / getMe validation. |
TELEGRAM_CHANNEL_NOT_CONNECTED | varies | Caller attempted a Telegram send (e.g. State 5 test send) but the org has no active row in public.telegram_bot_credentials. |
Email (test-mode gate)
| Code | HTTP | Description |
|---|---|---|
EMAIL_TEST_RECIPIENT_NOT_VERIFIED | varies | Shared-account test-mode recipient gate (mirrors WhatsApp test): only addresses on the tenant allowlist (saved contacts, verified org members, legacy OTP-verified entries) can receive shared-sender mail. Recovery ladder: EMAIL_TEST_RECIPIENT_NOT_VERIFIED runbook. |
Crypto envelope
| Code | HTTP | Description |
|---|---|---|
ENCRYPTION_FAILED | varies | AES-256-GCM enc:v1: envelope failed during write (random IV / authTag generation failure, libsodium / WebCrypto unavailable). Runbook: Troubleshooting: encryption envelope failures. |
DECRYPTION_FAILED | 502 | AES-256-GCM enc:v1: envelope failed during read (key rotation left a stale ciphertext, authTag mismatch, or the master key is missing). Runbooks: Troubleshooting: encryption envelope failures (platform master-key class) and BYOK key unavailable / decryption failed (BYOK-enforced class). |
Voice
| Code | HTTP | Description |
|---|---|---|
CALL_FAILED | 502 | 502 — generic voice call-control failure raised when the Jambonz REST control surface (jambonz-rest.ts) or the outbound call-create path cannot complete a call create / update / hangup. |
SIP_ERROR | varies | Reserved SIP-layer call-control error code. |
VOICE_GATEWAY_ERROR | 502 | 502 — the voice gateway (Jambonz / SBC) returned a transient error or was unreachable during a call-control or media operation (outbound dispatch, voicemail-greeting TTS synthesis + GCS upload, mid-call actions). Runbook: Troubleshooting: VOICE_GATEWAY_ERROR at call-send. |
TTS_SYNTHESIS_FAILED | 502 | Speech synthesis (TEXT-TO-SPEECH) failed upstream (502). |
STT_TRANSCRIPTION_FAILED | 502 | Speech-to-text transcription failed upstream (502). |
STT_CREDENTIAL_REJECTED | 401/403 | STT vendor rejected the credential on an STT Playground comparison run — the org’s BYO key is invalid, revoked, or missing permission. Deterministic: fix or revoke the key, do not retry. Runbook: Troubleshooting: transcription, synthesis, and deepfake failures. |
STT_PROVIDER_UPSTREAM | 5xx | STT vendor returned a server-side error on a Playground comparison run — provider-side outage, safe to retry with short backoff. Runbook: Troubleshooting: transcription, synthesis, and deepfake failures. |
VOICE_BLOCKED_DESTINATION | 422 | The destination number is on the voice blocked list—the call is refused pre-dial (422). |
VOICE_DNO_BLOCKED | 422 | Gap CSPAAS — Do-Not-Originate (DNO) origination-time block. |
EMERGENCY_CALLING_NOT_SUPPORTED | varies | Audit #E911-BLOCK-1 / invariant #49 — outbound voice rejected because the dialed number resolved to an emergency short code (911 / 112 / 999 / 000 — see EMERGENCY_NUMBERS_BLOCKED in @devotel/shared/constants). |
VOICE_RATE_LIMITED | 429 | The per-tenant voice outbound rate limit tripped (429). |
VOICE_COUNTRY_RATE_LIMITED | 429 | HTTP 429 — outbound voice rejected by the per-destination-country sliding-window rate cap in voice-fraud-guard.ts. |
SOFTSWITCH_UNHEALTHY | 503 | CCaaS dialer — wholesale softswitch / Jambonz transport-health circuit breaker tripped (failed-attempt rate above threshold in the recent window). |
VOICE_DAILY_SPEND_CAP | 422 | The per-tenant voice daily spend cap is reached (422). |
VOICE_TRANSFER_FAILED | 502 | 502 — the voice gateway rejected a mid-call transfer. |
VOICE_TRANSFER_TARGET_RESTRICTED | 403 | Per-agent transfer-policy gate blocked a transfer / warm-transfer / park / dialer-scheduled target (target kind or fine-grained extension/queue whitelist). details.matched_by names the deciding rule. Runbook: Troubleshooting: transfer target restricted. |
VOICE_CALLER_ID_REJECTED | varies | Customer-trunk INVITE rejected — caller-id not in allowedCallerIds nor an org-owned DID. |
VOICE_CHALLENGE_FAILED | varies | Caller-ID ownership-challenge (OTP verify/resend) failed; infra detail redacted before re-throw (r3 S14). |
UNVERIFIED_CALLER_ID | varies | R16FIX3_UNVERIFIED_CALLER_ID_GATE (2026-05-30) — outbound voice/SMS preflight refused the supplied from because it is neither a platform-owned/assigned DID for the org NOR a status='verified' row in the tenant’s verified_caller_ids… |
VOICE_TRUNK_HOURLY_CAP | varies | Customer-trunk hourly call cap exceeded for the trunk. |
VOICE_CLONE_CONSENT_REQUIRED | varies | Voice-clone creation refused for missing or invalid consent (SCAN-V-010). |
ELEVENLABS_OPTIONAL_CONFIG | 503 | 503 — ElevenLabs clone/design/upload entry point rejected because the optional DEVOTEL_ELEVENLABS_API_KEY env is unset on this deployment (Sentry DEVOTEL-ORBIT-159, culprit POST /api/v1/voice/clones/design). |
DTMF_UNSUPPORTED | varies | Mid-call DTMF inject not supported on this leg (B-237). |
RECORDING_TOGGLE_UNSUPPORTED | varies | Mid-call recording toggle not supported on this leg (B-242). |
EXPORT_TOO_LARGE | varies | CDR / call-log export filter would exceed the 100k row hard cap (B-240). |
TCPA_QUIET_HOURS | varies | Outbound voice call blocked by TCPA quiet-hours policy (08:00-21:00). |
TCPA_DIALING_WINDOW_BLOCKED | varies | Outbound voice call blocked by the TCPA dialing-window gate (8 AM–9 PM recipient-local). |
TCPA_FEDERAL_DIALING_WINDOW_BLOCKED | varies | Outbound voice call to a +1 NANP recipient blocked by the US TCPA FEDERAL dialing-window guard (47 U.S.C. |
TCPA_STATE_DIALING_WINDOW_BLOCKED | 422 | Outbound voice call blocked by a STATE mini-TCPA dialing-window or day-of-week restriction (e.g. FL Fla. |
FCC_AI_VOICE_WRITTEN_CONSENT_REQUIRED | varies | Outbound voice call blocked because the recipient has not granted “prior express WRITTEN consent” for AI-generated / synthesized / cloned voice content. |
QUIET_HOURS_BLOCKED | varies | Outbound non-voice send (SMS / MMS / WhatsApp / RCS / Email / Telegram / Viber / Instagram / Messenger / LINE / Apple Messages) blocked by the universal quiet-hours gate. Runbook: Messaging pre-send gates. |
TCPA_TIMEZONE_UNKNOWN | varies | Outbound voice call blocked because the recipient’s timezone could not be resolved (neither NANP area-code nor E.164 country prefix matched). |
QUIET_HOURS_TIMEZONE_UNKNOWN | varies | Non-voice channel equivalent of TCPA_TIMEZONE_UNKNOWN. |
DNC_CONTACT | varies | Recipient contact row has dnc=true. |
DNC_NUMBER | varies | Recipient number found in dnc_list table. |
COMPLIANCE_CHECK_UNAVAILABLE | varies | Compliance-layer DB check could not run. |
CAMPAIGN_VOICE_SPEND_CAP_REACHED | varies | Per-campaign voice-spend cap reached; campaign auto-paused. |
NOT_IMPLEMENTED | varies | Returned when a request targets a provider integration or capability that is defined but not yet generally available — e.g. a compliance submission for a carrier without a live provider, or an upstream SMPP carrier type reserved for a fu… |
FLIP_ALREADY_PULLED | 410 | 410 — POST /voice/call-flip/pull refused: the one-time call-flip handoff token was already pulled by the targeted device. |
FLIP_EXPIRED | 410 | 410 — call-flip pull refused: the handoff token expired before it was pulled. |
FLIP_TARGET_MISMATCH | 403 | 403 — call-flip pull refused: the handoff is pinned to a different target device than the caller’s. |
FLIP_NOT_FOUND | 404 | 404 — no live call-flip handoff matches the supplied pull token. |
FLIP_ALREADY_CONSUMED | 410 | 410 — call-flip cancel/pull refused: the handoff was already pulled or cancelled. |
PARK_SLOTS_FULL | 409 | 409 — auto-park refused: every park slot in the tenant’s park orbit is already occupied. |
PARK_SLOT_OCCUPIED | 409 | 409 — park-to-slot refused: the explicitly requested park slot is already occupied. |
PARK_ALREADY_RETRIEVED | 410 | 410 — park retrieve refused: the call previously parked in that slot was already retrieved. |
PARK_SLOT_EMPTY | 404 | 404 — park retrieve refused: no call is currently parked in the requested slot. |
PARK_ALREADY_RELEASED | 410 | 410 — park release refused: the parked call was already released. |
PARK_NOT_FOUND | 404 | 404 — parked call not found for the supplied identifier. |
HOT_DESK_RACE | 409 | 409 — hot-desk sign-in lost a concurrent race for the device and should be retried. |
HOT_DESK_ALREADY_SIGNED_OUT | 410 | 410 — hot-desk sign-out refused: the user is already signed out. |
HOT_DESK_NO_ACTIVE_SESSION | 404 | 404 — hot-desk sign-out refused: the user has no active hot-desk session. |
HOT_DESK_ALREADY_RELEASED | 410 | 410 — hot-desk session release refused: the session was already released. |
HOT_DESK_NOT_FOUND | 404 | 404 — hot-desk session not found for the supplied id. |
SECURE_PAYMENT_UNSUPPORTED | 422 | 422 — agent-assisted secure payment requires a Jambonz-bridged call leg and this call has none. |
SECURE_PAYMENT_ALREADY_ACTIVE | 409 | 409 — a secure-payment capture session is already active on this call. |
SECURE_PAYMENT_SESSION_NOT_FOUND | 404 | 404 — no secure-payment session with the supplied id exists on this call. |
SECURE_PAYMENT_NOT_ACTIVE | 409 | 409 — the secure-payment session is not in the capturing state (already completed or cancelled). |
SECURE_PAYMENT_INVALID_MASK | 422 | 422 — card_last4 must be exactly the last four digits of the PAN, never the full card number. |
MANDATE_CHARGE_FAILED | 500 | 500 — an in-call AP2 payment-mandate confirmation was authorized but the charge commit failed; verify the mandate scope (per-transaction max, merchant/category allow-list, status/expiry) before retrying. |
MANDATE_SPEND_UNVERIFIABLE | 503 | 503 — prior confirmed spend under the payment mandate could not be read or recorded, so the confirmation fails closed to protect the cumulative cap; no charge was attempted. |
IDEMPOTENCY_CONFLICT | 409 | 409 — a payment-mandate charge reference was already confirmed for a materially different cart (amount, currency, or merchant); stop and reconcile the reference — do not retry. |
PICKUP_NO_RINGING_CALL | 404 | 404 — POST /voice/pickup/directed (or /voice/pickup/group) found no ringing call to answer: nothing is ringing on the target extension, or no call is ringing in the caller’s pickup group. |
PICKUP_ALREADY_TAKEN | 409 | 409 — a call-pickup request lost the atomic claim race: another device answered the ringing call first. |
KBA caller verification
The seven codes below fire on the knowledge-based caller-verification flow (POST /api/v1/voice/kba/:callId/start, /verify, plus the sensitive
action gate). The consolidated runbook is
KBA caller-verification session errors;
the flow concept lives on
KBA caller verification.
| Code | HTTP | Description |
|---|---|---|
KBA_LOCKED | 409 | KBA start refused: the session on this call is terminal failed (attempt budget exhausted). Pass restart: true on start to open a fresh challenge. Runbook: KBA caller-verification session errors. |
KBA_SESSION_NOT_FOUND | 404 | KBA verify refused: no session exists on this call, or the submitted session_id is not the session the call record holds. Start a session first. Runbook: KBA caller-verification session errors. |
KBA_SESSION_NOT_ACTIVE | 409 | KBA verify refused: the session already reached verified or failed, so it no longer accepts answers. Read the ledger state before submitting. Runbook: KBA caller-verification session errors. |
KBA_NO_CONTACT_LINKED | 422 | KBA start refused: the call record carries no linked contact, so there is no on-file identity to challenge against. Link a contact to the call first. Runbook: KBA caller-verification session errors. |
KBA_CONTACT_NOT_FOUND | 422 | KBA start refused: the contact id linked to the call no longer resolves to a live contact row (deleted or unlinked). Re-link or pick another verification method. Runbook: KBA caller-verification session errors. |
KBA_INSUFFICIENT_PROFILE_DATA | 422 | KBA start refused: the linked contact has fewer than two verifiable fields (email, phone last 4, full name, company) on file — switch to an OTP send, agent judgement, or voice biometrics. Runbook: KBA caller-verification session errors. |
KBA_VERIFICATION_REQUIRED | 403 | A sensitive in-call action (payment capture, PII disclosure, account change) refused the call because it has no verified, unexpired KBA session. Run start → verify, then retry once. Runbook: KBA caller-verification session errors. |
Video rooms
| Code | HTTP | Description |
|---|---|---|
VIDEO_ROOM_CREATE_FAILED | varies | Returned when an Orbit Media call fails during video-room creation (immediate path). |
VIDEO_ROOM_OPEN_FAILED | varies | Returned when an Orbit Media call fails during the lazy-open of a scheduled room (first host/participant join). |
VIDEO_PARTICIPANT_KICK_FAILED | varies | Returned when the Orbit Media removeParticipant call fails while removing a participant from a room. |
VIDEO_PARTICIPANT_MUTE_FAILED | varies | Returned when the Orbit Media mutePublishedTrack call fails while muting a participant’s track. |
VIDEO_RECORDING_STOP_BACKEND_FAILED | varies | Returned when the Orbit Media stopEgress call rejects while stopping a room recording. |
VIDEO_WEBHOOK_VERIFICATION_FAILED | varies | Returned when the Orbit Media webhook JWT signature cannot be verified. |
RECORDING_SIGN_FAILED | 503 | P2-shard-F (2026-06-02) — Returned when RecordingService.generateSignedUrl fails to mint a V4 signed GCS playback URL (GCS auth failure, IAM denial, transient GCS outage, ADC misconfiguration). |
VIDEO_BACKGROUND_INVALID | 400 | 400 — POST /video/backgrounds refused to register a branded virtual-background asset because the submitted asset failed library-side validation (e.g. duplicate name, malformed/blocked URL, or the per-tenant background limit was reached). |
VIDEO_BACKGROUND_NOT_FOUND | 404 | 404 — DELETE /video/backgrounds/:assetId could not find a registered virtual-background asset with that id in the tenant’s background library. |
AGENT_NOT_DEPLOYABLE | 409 | 409 — POST /video/rooms/:id/dispatch-agent refused to dispatch an AI agent into a room because the referenced agent is not in the active state. |
Call recordings (QC / legal-hold / integrity / share)
| Code | HTTP | Description |
|---|---|---|
RECORDING_QC_FETCH_FAILED | 500 | 500 — GET /recordings/:id/qc could not load the stored QC verdict for a call recording. |
RECORDING_QC_RUN_FAILED | 500 | 500 — POST /recordings/:id/qc/run failed while synchronously re-running the recording QC scorer (the scorer threw, or a DB write failed). |
RECORDING_QC_INVALID_ID | 400 | 400 — the :id path param on a GET /recordings/:id/qc or POST /recordings/:id/qc/run operation failed param validation (empty / over the length cap). |
RECORDING_QC_NOT_FOUND | 404 | 404 — GET /recordings/:id/qc or POST /recordings/:id/qc/run received a syntactically valid recording id that matched no recording row in the tenant schema (missing / tombstoned). |
RECORDING_LEGAL_HOLD_FETCH_FAILED | 500 | 500 — GET /recordings/:id/legal-hold could not load the legal-hold state for a recording (DB / tenant-schema fault). |
RECORDING_LEGAL_HOLD_SET_FAILED | 500 | 500 — PUT /recordings/:id/legal-hold failed to place or release a legal hold (the compliance-governance write threw after passing validation / auth). |
RECORDING_INTEGRITY_FETCH_FAILED | 500 | 500 — GET /recordings/:id/integrity could not load the recording integrity seal (DB / tenant-schema fault). |
RECORDING_INTEGRITY_VERIFY_FAILED | 500 | 500 — the recording integrity-seal verification path failed unexpectedly (the recompute-and-compare digest check threw before returning a pass/fail verdict). |
RECORDING_INTEGRITY_EXPORT_DIGEST_FAILED | 500 | 500 — building the signed export digest for a recording’s integrity evidence failed unexpectedly (digest assembly / signing threw). |
RECORDING_INTEGRITY_SEAL_FAILED | 500 | 500 — sealing a recording (minting and persisting its tamper-evidence integrity seal) failed unexpectedly. |
RECORDING_SHARE_CREATE_FAILED | 500 | 500 — POST /recordings/:id/shares could not create a share link for a recording (DB write / token-mint fault after validation + auth). |
RECORDING_SHARE_REVOKE_FAILED | 500 | 500 — revoking a recording’s share link(s) failed unexpectedly (the revoke write threw). |
Agent
| Code | HTTP | Description |
|---|---|---|
AGENT_ERROR | 502 | An agent operation failed mid-execution (502). |
AGENT_NOT_CONFIGURED | 422 | The agent lacks required configuration (e.g. a system prompt) before this operation (422). |
AGENT_RESPONSE_INVALID | 502 | The model’s structured response failed schema validation (502). |
INVALID_MODEL | 422 | The requested model isn’t in the allowed model list (422). |
LLM_PROVIDER_ERROR | 502 | The LLM provider returned an error—honor the retryable flag (502). |
LLM_TIMEOUT | 504 | 504 — An AI-generation endpoint’s LLM round-trip exceeded its abort deadline (AbortSignal.timeout). |
LLM_UPSTREAM_ERROR | 502 | 502 — An AI-generation endpoint’s upstream LLM call failed with a non-timeout provider/transport error. |
AI_PARSE_FAILED | 422 | 422 — An AI-generation endpoint received model output that could not be parsed into the required structured shape (non-JSON / schema mismatch). |
GUARDRAIL_VIOLATION | varies | Reserved AI-guardrail error code. |
GRAPH_VALIDATION_FAILED | 422 | 422 — Agent Studio graph publish validation failed; the submitted graph did not pass structural validation and details carries the per-node validation errors. |
GRAPH_TOOL_NODES_NOT_SUPPORTED_IN_TEST_RUN | 422 | 422 — Agent Studio test-run was asked to execute a graph containing tool nodes, which the sandboxed test-run does not execute; publish the graph and invoke the agent’s /run endpoint instead. |
RESERVED_TOOL_NAME | 422 | 422 — Custom-tool registration rejected because the requested tool name is reserved for a built-in platform tool; pick a tenant-specific slug. |
INVALID_EXECUTOR_URL | 422 | 422 — Custom-tool executor_url rejected by the SSRF write-time guard (non-https, private/loopback, cloud-metadata host, or a DNS record that resolves internally). |
TOOL_NAME_TAKEN | 409 | 409 — Custom-tool create collided with an existing custom tool of the same name in the tenant. |
TOOL_DISABLED | 409 | 409 — Custom-tool invocation refused because the referenced custom tool is disabled. |
INVALID_MCP_SERVER_URL | 422 | 422 — MCP-server registration rejected by the SSRF write-time guard on the server URL (non-https, private/loopback, cloud-metadata host, or a DNS record that resolves internally). |
INVALID_MCP_OAUTH_TOKEN_URL | 422 | 422 — MCP-server registration rejected by the SSRF write-time guard on the OAuth2 grant token_url (non-https, private/loopback, cloud-metadata host, or a DNS record that resolves internally). |
MCP_SERVER_NAME_CONFLICT | 409 | 409 — MCP-server registration collided with a server of the same name already registered for the agent (uq_mcp_servers_agent_name). |
LABELING_NO_INPUT | 422 | 422 — the agent fine-tuning labeling endpoint had nothing to label: the referenced conversation carries no user message and the caller did not supply an explicit input. |
LABELING_DATASET_FULL | 409 | 409 — the agent’s labeling dataset has reached its row ceiling, so no further labeled rows can be added. |
Agent Eval
| Code | HTTP | Description |
|---|---|---|
EVAL_COMPARE_DATASET_MISMATCH | varies | compareEvalRuns: run_a_id and run_b_id reference different datasets. |
EVAL_COMPARE_SAME_RUN | varies | compareEvalRuns: run_a_id === run_b_id. |
EVAL_RUNS_AGENT_MISMATCH | varies | compareEvalRuns: one or both runs belong to a different agent (SCAN-AGT-R2-010). |
EVAL_GATE_FAILED | 422 | 422 — Agent Studio publish blocked by the eval gate because the agent has no completed eval run meeting the configured minimum pass rate; run an eval against the agent before publishing. |
EVAL_REGRESSION_GATE_FAILED | 422 | 422 — Agent Studio publish blocked by the eval regression gate because the latest eval run regressed beyond the configured tolerance versus the baseline run; investigate and re-run evals before publishing. |
Billing
| Code | HTTP | Description |
|---|---|---|
PAYMENT_REQUIRED | 402 | Payment is required before this operation can proceed (402). |
BUDGET_EXCEEDED | varies | The configured budget (e.g. LLM token budget) is exhausted (422 or 402). |
BILLING_PROVIDER_ERROR | 502 | The billing provider returned an error (502). |
EMAIL_INTAKE_PARSE_ERROR | 422 | 422 — the admin rate-card email-intake preview could not parse the supplied raw MIME message into rate-card uploads (unreadable MIME, unsupported attachment, or an unparseable rate table). |
VOICE_BILLING_HOLD_FAILED | 503 | Security fix 2026-05-20 (exploit-voice-billing-hold-fail-open): Returned (503) when placeVoiceBillingHold cannot enforce spending controls due to an infrastructure error (Redis down, Lua error, etc.). |
VOICE_BILLING_CURRENCY_DEFERRED | varies | BIL-DEEP-012 (escalated P1, 2026-06-02): tenant wallet-currency could not be resolved at voice settle time (DB lookup threw / org row missing), AND the hold did not stamp a voice_billing.tenant_currency. |
SENDING_PAUSED | varies | A customer-configured billing_alerts rule with action=‘pause_outbound’ tripped; outbound sending is temporarily paused until manually reset. |
SENDING_BLOCKED | varies | A customer-configured billing_alerts rule with action=‘block_outbound’ tripped; outbound sending is hard-blocked until manually reset or the next billing cycle. |
FREQUENCY_CAP_EXCEEDED | 429 | Per-org frequency-cap rule hit — too many sends to this recipient on this channel inside the configured window. |
MESSAGING_SERVICE_MPS_EXCEEDED | 429 | PARITY-R1-016 — per-MessagingService throughput cap (Twilio MessagingService MPS parity) hit on the API-direct send path. |
NO_REACHABLE_CHANNEL | 422 | 422 — POST /billing/pay-by-link (and its /preview companion) could not select a delivery channel for the hosted-checkout link: none of the caller’s preferred_channels appears in reachable_channels, so there is no path to reach th… |
TRIAL_CREDIT_CAP_EXCEEDED | 422 | 422 — POST /billing/trial-credits refused a promotional trial-credit grant because the requested amount exceeds the effective per-tenant trial-credit cap (the platform default, optionally lowered — never raised — by a tenant override). |
TRIAL_CREDITS_FAILED | 500 | 500 — POST /billing/trial-credits failed to grant a promotional trial-credit grant because of an upstream infrastructure fault while applying the grant. |
Presence federation
| Code | HTTP | Description |
|---|---|---|
PRESENCE_FEDERATION_LIST_FAILED | 500 | 500 — GET /users/me/presence/federation could not read the caller’s federation source rows from storage. Safe to retry; the recorded sync state is untouched. |
PRESENCE_FEDERATION_UPSERT_FAILED | 500 | 500 — PUT /users/me/presence/federation/:provider could not persist an opt-in / toggle. Idempotent and safe to retry. |
PRESENCE_FEDERATION_DELETE_FAILED | 500 | 500 — DELETE /users/me/presence/federation/:provider could not remove the source row. One retry is safe (delete is idempotent); a persistent failure needs support. Runbook: Presence federation degraded states and API failures. |
Tenant / Multi-tenancy
| Code | HTTP | Description |
|---|---|---|
MISSING_TENANT | 400 | The request lacked a resolvable tenant context (400). |
System
| Code | HTTP | Description |
|---|---|---|
INTERNAL_ERROR | 500 | Unexpected platform error—retry; include meta.request_id if it persists (500). |
SERVICE_UNAVAILABLE | 503 | The platform or an upstream dependency is temporarily unavailable (503). |
UNDER_PRESSURE | 503 | The platform’s ELU-based load-shed answered: the server is temporarily overloaded — safe to retry after Retry-After seconds. Distinguishable from the general SERVICE_UNAVAILABLE (a systemic dependency fault). See Troubleshoot 503 UNDER_PRESSURE. |
SESSION_REPLAY_OFFLOAD_FAILED | 503 | 503 — the widget session-replay ingest endpoint could not offload the batch’s overflow events to durable storage (the spill-to-GCS path that keeps replay capture lossless). |
ADMIN_ORG_MISSING_TENANT | 422 | Admin attempted an operation on an org whose tenant_id is NULL and whose slug also fails to derive a usable tenant — fail-fast 422 so the admin operator surfaces this to platform engineering instead of silently writing to a wrong tenant… |
BALANCE_SERVICE_UNAVAILABLE | 503 | Balance / credit-resolver service is temporarily unavailable because the Redis idempotency dedup layer is unreachable. |
SMPP_BACKEND_UNAVAILABLE | 503 | SMPP credential issuance / rotation failed because a backing dependency (tenant DB schema, encryption key, Jasmin reconciler queue) is temporarily unavailable. Runbook: SMPP_BACKEND_UNAVAILABLE. |
A2A_PEER_ERROR | 502 | Returned (502) when an outbound A2A (Agent-to-Agent) federation call to a peer agent failed — peer offline, peer card unreachable, peer JSON-RPC returned a non-2xx, or peer task envelope was malformed. |
A2A_DISCOVERY_DISABLED | 422 | Issue #259 (TU-230 deep-tail) — the dashboard A2A tab requested a card for an agent whose a2a_discovery_mode === 'disabled' (the default). |
WEBHOOK_DELIVERY_FAILED | 502 | The outbound webhook delivery failed (502). |
WEBHOOK_SIGNATURE_INVALID | 401 | Webhook signature verification failed—check the shared secret (401). |
WEBHOOK_SECRET_DECRYPT_FAILED | 503 | Wave-W1 CPA-011 — Webhook signing secret could not be decrypted because the stored value carried the platform enc:v1: envelope but the active DEVOTEL_ENCRYPTION_KEY no longer matches (rotation pending re-encrypt) OR the ciphertext is… |
WEBHOOK_AUTH_FAILED | varies | Wave-W1 CPA-005 (P0) — uniform shape for webhook authentication failures on unauthenticated provider webhooks (Meta WhatsApp Flows /endpoint, etc.). |
WEBHOOK_PROCESSING_FAILED | 500 | The webhook receiver threw while processing (500). |
INVALID_WEBHOOK_URL | 422 | The webhook URL failed validation (must be http/https and not private/link-local) (422). |
WEBHOOK_DNS_INVALID | 422 | Audit #PRV-001 (2026-05-12) — webhook-endpoint URL DNS-resolves to NXDOMAIN / NODATA / SERVFAIL. |
WEBHOOK_ENDPOINT_CAP_REACHED | varies | Tenant tried to create more than MAX_WEBHOOK_ENDPOINTS_PER_TENANT webhook endpoints (default 10). |
BULK_REPLAY_IN_PROGRESS | 409 | 409 — POST /webhooks/:id/replay-range (webhook bulk-replay) refused to start a new bulk-replay because one is already running or queued for the same endpoint. |
ENDPOINT_GONE | 404 | API-CONTRACT (admin DLQ webhook replay) — POST /admin/dlq/webhooks/:id/replay refused to re-enqueue because the target webhook endpoint the DLQ job was destined for has since been deleted or disabled (endpointPresence returned `“gone… |
ENDPOINT_LOOKUP_FAILED | 503 | API-CONTRACT (admin DLQ webhook replay) — companion to ENDPOINT_GONE. |
STRIPE_CUSTOMER_ORG_MISMATCH | varies | Audit #B-004 (2026-05-11) — payment_intent.succeeded webhook with forged metadata.orgId pointing at a victim tenant, paid by an attacker’s Stripe customer on the shared platform account. |
BUDGET_REDIS_UNAVAILABLE | 503 | Audit #B-040 (2026-05-11) — reserveBudget failed CLOSED because Redis was unavailable or the Lua eval threw. |
LLM_PLATFORM_CEILING_EXCEEDED | 503 | Security fix 2026-05-20 (exploit-llm-budget-no-platform-ceiling): Returned (503) when a new reservation would push the platform-wide aggregate monthly LLM spend above the configured ceiling. |
TENANT_NOT_FOUND | 404 | The resolved tenant doesn’t exist (404). |
TENANT_PROVISIONING | 503 | Returned (503) by the auth middleware when an org row exists but its tenant_schema_ready flag is still FALSE — meaning the baseline schema creation or the per-tenant migration run has not finished (or failed). |
TENANT_SCHEMA_INCOMPLETE | 503 | Returned (503) when a tenant-schema-scoped query hits SQLSTATE 42P01 (undefined_table) on a table introduced by a recent feature migration. |
RECIPIENT_OPTED_OUT | 422 | The recipient has opted out—remove them from the recipient list (422). |
CONTACT_BLOCKED | varies | Contact is blocked at the org level — every outbound send rejected. Runbook: Messaging pre-send gates. |
CONTACT_ERASURE_PENDING | varies | Contact has a pending GDPR Article-17 erasure request in its 7-day cooling-off window. |
INVALID_FILE_TYPE | varies | File upload rejected at the FE pre-flight — MIME / extension mismatch. |
FILE_TOO_LARGE | varies | File upload rejected — size exceeds the configured cap. |
MAGIC_BYTE_MISMATCH | varies | File upload rejected — magic-byte signature didn’t match the declared MIME. |
INSUFFICIENT_BALANCE | 402 | The tenant balance is too low to cover this operation—top up first (402). |
INSUFFICIENT_CREDITS | varies | @deprecated Use INSUFFICIENT_BALANCE instead |
DEDUCT_IN_FLIGHT | varies | Returned by deductBalance / addBalance when another caller already holds the same idempotency key in Redis but has not yet posted its final result within the poll window (2 s today). |
RESELLER_MARGIN_RESERVE_FAILED | 503 | 503 — the pre-deduct reseller-margin pending-row reservation (reserveResellerMarginPending) failed, so the send is aborted BEFORE the child wallet is debited. |
FORBIDDEN | 403 | The caller (API key or session) doesn’t have access to this resource (403). |
INVALID_STATE | 422 | The conversation or entity is in a state that doesn’t allow this operation (422). |
CONVERSATION_TERMINAL | 422 | SCAN-CINB-010 + SCAN-CINB-011 — ConversationsService.resumeAi and ConversationsService.handoff reject calls against conversations in a terminal/non-actionable status (closed, archived, snoozed). |
TAKEOVER_NOT_ALLOWED | 422 | Supervisor take-over (422) refused on a terminal/closed digital conversation — there is no live conversation to intercept; details carries the current { status }. |
TAKEOVER_ALREADY_ACTIVE | 409 | Supervisor take-over (409) rejected because a take-over is already active on the conversation; details carries { takeover_id, supervisor_id } so the UI can show who is already in control. |
NO_ACTIVE_TAKEOVER | 409 | Supervisor take-over (409) error returned when a send-reply or release is attempted but no take-over is currently active on the conversation. |
TAKEOVER_FORBIDDEN | 403 | Supervisor take-over (403) denied: the caller neither owns the active take-over nor is an elevated owner/admin recovering it. |
REAUTH_REQUIRED | varies | audit #511 backend top-10 #2/#7 — fresh re-authentication required for irreversible operations (GDPR delete, 2FA disable, HIPAA toggle). |
ROLLBACK_WINDOW_EXPIRED | varies | audit #511 backend top-10 #3 — import rollback past the 24h window. |
INVITE_EXPIRED | varies | audit #511 backend top-10 #8 — invitation token has expired. |
AUTO_TOPUP_MONTHLY_CAP_REACHED | varies | Auto-top-up cron tried to recharge but the org’s max_monthly_minor cap has already been reached this calendar month. |
AUTO_TOPUP_PAYMENT_FAILED | varies | Off-session Stripe PaymentIntent for an auto-top-up failed (card declined, no default payment method, etc.). |
SUPERVISOR_ACTION_FAILED | 502 | A supervisor action (watch / coach / barge / take-over) failed (502). |
SUPERVISOR_TAKEOVER_FAILED | 500 | 500 — POST /voice/supervisor/takeovers (initiate a voice supervisor takeover / whisper-coach session) failed unexpectedly. |
SUPERVISOR_TAKEOVER_LIST_FAILED | 500 | 500 — GET /voice/supervisor/takeovers (list voice supervisor takeovers) failed unexpectedly. |
SUPERVISOR_TAKEOVER_GET_FAILED | 500 | 500 — GET /voice/supervisor/takeovers/:id (fetch a single voice supervisor takeover) failed unexpectedly. |
SUPERVISOR_TAKEOVER_UPDATE_FAILED | 500 | 500 — PATCH /voice/supervisor/takeovers/:id (advance state or cancel a voice supervisor takeover) failed unexpectedly. |
SUPERVISOR_WHISPER_TEXT_FAILED | 500 | 500 — POST /voice/supervisor/calls/:id/whisper-text (whisper a text coaching line to the live agent) failed unexpectedly. |
SUPERVISOR_ASSIST_STREAM_ERROR | 500 | 500 — GET /voice/supervisor/calls/:callId/assist/stream (supervisor AI-assist SSE card stream) failed unexpectedly. |
DIGITAL_TAKEOVER_START_FAILED | 500 | 500 — POST /conversations/:id/takeover/start (supervisor seizes reply control on a digital conversation) failed unexpectedly. |
DIGITAL_TAKEOVER_REPLY_FAILED | 500 | 500 — POST /conversations/:id/takeover/send-reply (reply under an active digital takeover) failed unexpectedly. |
DIGITAL_TAKEOVER_END_FAILED | 500 | 500 — POST /conversations/:id/takeover/end (release a digital takeover back to the agent) failed unexpectedly. |
DIGITAL_TAKEOVER_GET_FAILED | 500 | 500 — GET /conversations/:id/takeover (read current digital takeover state) failed unexpectedly. |
ONLY_ADMIN | 422 | Audit #SET-003 / #SET-004 — refuses an offboarding operation (SCIM deleteUser, settings removeTeamMember, danger-zone org-deletion) when the target is the only remaining admin/owner of the organisation. |
INSUFFICIENT_SCOPE | 403 | API key was rejected because the declared scope set does not include the scope required by the requested route. |
CALLING_REQUIRES_ELIGIBLE_PHONE | 422 | WhatsApp Business Calling needs a phone whose country is NOT in Meta’s calling-blocked list (US/CA/EG/VN/NG today). |
FEATURE_DISABLED | 422 | This feature is turned off for the tenant—enable it before use (422). |
ACCOUNT_LOCKED | 403 | The account is locked—contact support (403). |
AGENT_EXECUTION_TIMEOUT | 504 | The agent’s execution exceeded its time budget (504). |
PAYLOAD_TOO_LARGE | 413 | The request body exceeds the size limit—reduce the payload (413). |
LOOKUP_FAILED | 502 | The phone-number lookup (e.g. carrier / line type) failed upstream (502). |
REQUEST_ABORTED | varies | Request cancelled by caller (e.g. campaign pause aborted in-flight provider send). |
POLICY_VIOLATION | varies | Pre-send policy scan blocked the message (TCPA / SHAFT / GDPR). |
POLICY_SCAN_MODE_LOOKUP_FAILED | 503 | SCAN-MSG-DEEP-002 (2026-05-26) — resolvePolicyScanMode could not read organizations.policy_scan_mode from Postgres AND the per-org 5-minute Redis cache is cold. |
BALANCE_OPERATION_FAILED | varies | Admin balance adjust operation (deduct/add) failed at the credits layer. |
BALANCE_CAP_EXCEEDED | 422 | SCAN-BILL-R3-013 (2026-05-23) — addBalance rejected because the resulting wallet balance would exceed MAX_BALANCE_CENTS. |
BILLING_TENANT_UNRESOLVED | 500 | Returned (500) when resolveBillingTenantId produced an empty or still-schema-shaped value on a money-touching path (number purchase, deduct, top-up). |
PRICING_NOT_CONFIGURED | varies | Wave P1-A (2026-05-18) — resolvePrice() could not find a rate row OR an org override for the (channel, sub_type, country, mcc, mnc, prefix) tuple being charged. |
PRICING_FX_NOT_CONFIGURED | varies | Wave P1-A (2026-05-18) — Open Exchange Rates app_id env var is unset but a currency conversion is required. |
PRICING_FX_STALE | varies | Wave P1-A (2026-05-18) — most-recent FX snapshot for the required currency pair is older than the 7-day hard ceiling. |
PRICING_FX_UPSTREAM_FAILED | varies | Wave P1-A (2026-05-18) — Open Exchange Rates returned a non-2xx response while warming a missing rate snapshot. |
PRICING_FX_UNEXPECTED_BASE | varies | Wave P1-A (2026-05-18) — OXR returned a base other than USD when the free-tier API contract guarantees USD-base. |
PRICING_FX_UNSUPPORTED_CURRENCY | varies | Wave P1-A (2026-05-18) — caller requested a currency the supported set doesn’t cover. |
PRICING_RATE_BELOW_WHOLESALE_COST | varies | A per-org absolute price override was below the platform’s active wholesale cost for that channel/country/direction lane, which would bill the tenant less than the cost of delivering on their behalf. |
PRICING_RATE_SANITY_VIOLATION | varies | A manual rate update moved the cost more than 5x above, or below 20% of, the lane’s current active cost. |
PRICING_RATE_SLOT_CONFLICT | varies | A single manual rate create/patch would collide with another operator’s (or prefix’s) already-active rate on the SAME pricing_rates partial unique-index slot (channel + country + direction [+ sub-type]). |
PRICING_OVERRIDE_SLOT_CONFLICT | varies | POST /admin/pricing/overrides (AdminPricingService.createOverride) would create a SECOND active per-org override on the exact same (organizationId, channel, subType, countryCode, direction) scope as one that already exists. |
PRICING_UPLOAD_STALE_CONFLICT | varies | A cached rate-file preview went stale before it was applied — the rate card changed in the apply window (e.g. another admin applied a conflicting rate), so the batched INSERT would collide with a pricing_rates partial unique index. Runbook for the whole PRICING_* family: Troubleshoot pricing-gate errors. |
CUSTOM_FIELD_IN_USE | varies | Returned when deleting a custom-field definition would break segments that reference it. |
CONCURRENCY_LIMIT_EXCEEDED | varies | Returned when a voice outbound-call request would exceed the per-org concurrency cap (organizations.voice_max_concurrent). |
CONFERENCE_DIAL_ALL_FAILED | 502 | Audit #CONF-CALLER-ID — every seed participant on POST /voice/conferences failed to dial via Jambonz. |
CONFERENCE_DIAL_PARTIAL | varies | Audit #CONF-CALLER-ID — partial-success companion to CONFERENCE_DIAL_ALL_FAILED. |
CONF_INVALID_PARTICIPANT | varies | wave_conf_core_2026_05_18 — TR-27 — conference create failed because one of the supplied participant numbers did not parse as a valid E.164 phone number. |
CONF_CALLER_ID_NOT_OWNED | varies | wave_conf_core_2026_05_18 — TR-27 — conference create’s from caller-id is not owned by the calling tenant. |
CONF_TRUNK_UNREGISTERED | 503 | wave_conf_core_2026_05_18 — TR-27 — conference create failed because the upstream voice trunk (Devotel softswitch / Jambonz REST) was unregistered or unreachable at dial time. |
CONF_NOT_FOUND_OR_NOT_ACTIVE | 404 | Conference row not found OR found but status != 'active'. |
CONFERENCE_PIN_REQUIRED | 401 | DECISION — UCAAS_003_018_CONFERENCE_PIN_2026_05_28. |
CONFERENCE_PIN_MISMATCH | 401 | DECISION — UCAAS_003_018_CONFERENCE_PIN_2026_05_28. |
CONFERENCE_STATE_UNRESOLVED | 409 | The conference lock was refused because the room has not settled into a live state: no participant is connected while one or more legs are still dialing (pending, dialing, or ringing). |
CONF_AI_AGENT_ALREADY_ACTIVE | 409 | Conference already has an active AI-agent attached. |
CONF_AI_AGENT_NOT_FOUND | 404 | Conference AI-agent attachment row not found. |
INVALID_DESTINATION | 422 | SIP softphone outbound destination failed validation. |
SIP_TRUNK_UNREGISTERED | 503 | Historical code — since 2026-05-21 the dial gate falls back to the Devotel wholesale softswitch (or the operator-configured failover chain) instead of refusing. Runbook: Troubleshooting: SIP trunk — unregistered at dial time. |
OUTSIDE_SESSION_WINDOW | 422 | Outbound message rejected because the 24h customer-service-window is closed (WhatsApp/Messenger). Runbook: Messaging pre-send gates. |
VOICE_TRUNK_CPS_CEILING | 429 | Customer SIP trunk hit its CPS (calls-per-second) ceiling. |
VOICE_TRUNK_CONCURRENT_CAP | 503 | Customer SIP trunk hit its concurrent-call cap. |
INTERNAL_COMPLIANCE_ERROR | 503 | Compliance scanner threw — fail-closed surface. |
NUMBER_ALREADY_TAKEN | 409 | Phone-number purchase race — another tenant already claimed it. |
INVALID_REQUEST | 422 | Generic onboarding-wizard validation failure (typed as a Zod error already, this is the API code). |
DELIVERY_FAILED | 503 | Verify channel send returned a provider failure (upstream/softswitch rejected the submit). |
ALL_CHANNELS_FAILED | 503 | Verify fan-out exhausted all configured channels without one succeeding. |
VERIFY_FRAUD_BLOCKED | 403 | 403 — Verification refused by Fraud Guard: the recipient destination is blocked by the tenant’s anti-fraud policy (geo / high-risk operator / composite risk score). |
VERIFY_RND_REASSIGNED | 422 | 422 — Verification refused by the FCC Reassigned Numbers Database (RND) pre-send guard: the destination phone number (sms / mms / voice / flashcall, US +1) has a permanent-disconnect record in the RND feed and may have been reassigned to… |
VERIFY_NUMBER_DEACTIVATED | 422 | 422 — Verification refused by the carrier phone-number DEACTIVATION (churn) pre-send guard: the destination phone number (sms / mms / voice / flashcall, NANP +1) has a deactivation record in the carrier feed (disconnect-presence — no con… |
VERIFY_SEND_FAILED | varies | Per-recipient failure code in a bulk-verify response row (not an HTTP status). |
CONF_PROVIDER_ERROR | 502 | wave_conf_core_2026_05_18 — TR-27 — conference create’s Jambonz REST call returned a non-2xx that wasn’t structurally a VALIDATION/AUTH error. |
CONF_INSUFFICIENT_CREDIT | 402 | wave_conf_core_2026_05_18 — TR-27 — conference create denied at the billing-hold step (caller has no remaining credit / hit a spend cap). |
NUMBER_NO_LONGER_AVAILABLE | 409 | Returned (409) when a phone number a tenant tried to purchase has been claimed by someone else between the inventory list-fetch and the purchase request. |
NUMBER_PROVISIONING_FAILED | 502 | Returned (502) when a number was successfully purchased upstream and billed but a downstream platform-side step (e.g. mirroring into public.platform_numbers so inbound traffic can resolve the owning tenant) failed. |
INVALID_RING_GROUP_CYCLE | 422 | Returned (422) when a ring-group create/update would introduce a cycle reachable from the group itself (A → B → A or A → B → C → A). |
COMPLIANCE_PROFILE_NOT_APPROVED | 422 | Returned (422) when a feature surface (number purchase, sender-ID registration, brand registration, etc.) tries to attach a compliance profile whose status is not ‘approved’. |
COMPLIANCE_PROFILE_REQUIRED | 422 | Returned (422) when a number-purchase request targets a regulated country (UK / DE / FR / etc.) AND the tenant has no approved compliance profile that covers it. |
PLACEHOLDER_REQUIRES_PROFILE | 422 | Returned (422) when a tenant tries to buy a DIDWW DID-group PLACEHOLDER row (did_group:<uuid>) WITHOUT an approved compliance profile for the row’s country. |
COMPLIANCE_PROFILE_INCOMPLETE | 422 | Returned (422) when a tenant tries to submit a compliance profile that is missing required documents or data fields per the compliance-requirements registry’s RequirementSet for that use_case + country. |
COMPLIANCE_PROFILE_IN_USE | 409 | Returned (409) when a tenant tries to delete a compliance profile, or detach an underlying document, that’s still referenced by an active downstream entity (purchased phone number, registered sender ID, brand, campaign, etc.). |
COMPLIANCE_PROFILE_LOCKED | 409 | Returned (409) when the tenant tries to mutate a compliance profile (data, documents, fields) that has already moved past draft — once submitted to providers we lock the payload to keep our state in sync with what each carrier holds. |
COMPLIANCE_USE_CASE_NOT_SUPPORTED | 422 | Returned (422) when a tenant tries to submit a compliance profile whose use-case fans out only to NOT_IMPLEMENTED provider shells (TCR / Meta WA / Google RCS at the time of writing). |
BRAND_NOT_APPROVED | varies | Tenant tried to submit an RCS bot under a brand that hasn’t been approved yet. |
SIM_SWAP_DETECTED | 403 | Returned (403) by /verify/start when a SIM-swap event is detected inside the block window (24h default) on the recipient MSISDN. |
MESSAGING_TR_URL_STRIP_VIOLATION | 422 | Türkiye BTK Decision 2025/DK-YED/412 (effective 1 Apr 2026) blocks A2P SMS containing URLs from senders not registered in Türkiye — the carrier strips the entire payload at delivery so the recipient sees nothing and the operator burns sp… |
RECORDING_CONSENT_REQUIRED | varies | Two-party recording-consent gate (UAE Penal Code Art. |
RECORDING_CONSENT_INVALID | 422 | Recording-consent receipt failed DB-backed validation (SCAN-RVV-008). |
MESSAGING_IN_DLT_TEMPLATE_REQUIRED | varies | India TRAI DLT (Distributed Ledger Technology) registration requires an approved dlt_template_id for every A2P SMS routed to IN destinations. |
MESSAGING_IN_DLT_CONTENT_MISMATCH | 422 | 422 — India TRAI DLT content-template enforcement: an A2P SMS carries an approved dlt_template_id but its body or sender header does not match the registered template, so Orbit fails the send closed at the platform edge. |
MESSAGING_BR_SENDER_NOT_REGISTERED | varies | Brazil Anatel sender-ID registration is required for any operator sending A2P SMS to BR destinations. |
MESSAGING_SA_SENDER_NOT_REGISTERED | varies | Saudi Arabia CITC sender-name registration is required for any operator sending A2P SMS to KSA (SA) destinations. |
MESSAGING_AE_SENDER_NOT_REGISTERED | varies | United Arab Emirates TDRA sender-name registration is required for any operator sending A2P SMS to UAE (AE) destinations. |
MESSAGING_SG_SENDER_NOT_REGISTERED | varies | Singapore SGNIC (Sender ID Registry) registration is required for any operator sending alphanumeric A2P SMS to Singapore (SG) destinations. |
MESSAGING_MX_NOM184_CONSENT_MISSING | varies | Mexico NOM-184-SCFI requires explicit, time-stamped consent for every promotional A2P SMS to a MX subscriber. |
CONSENT_RECEIPT_INVALID | varies | India DPDP Phase II Consent-Manager receipt verification failed: either the receipt id is unknown to the platform, the signature does not match the registered manager’s public key, or the receipt has been tampered with. |
MESSAGING_TCPA_KNOWN_LITIGATOR | 403 | Recipient phone is on the platform’s known-litigator list (TCPA professional plaintiff). |
MESSAGING_CONSENT_REQUIRED | varies | General pre-send affirmative-opt-in (consent) gate. |
MESSAGING_NUMBER_DEACTIVATED | 422 | 422 — SMS/MMS send refused by the carrier phone-number DEACTIVATION (churn) list-hygiene pre-send guard: the recipient’s carrier reported the destination (NANP +1) deactivated on/after the sender’s most-recent consent_records grant dat… Runbook: Messaging pre-send gates. |
TFV_REQUIRED | varies | US toll-free numbers must complete carrier-side Toll-Free Verification (TFV) before they can send A2P SMS to US carriers without aggressive throttling / outright blocking. |
TEN_DLC_NOT_REGISTERED | varies | Audit #COMP-002 — US 10DLC (10-digit long code) send rejected because the org has no approved TCR (The Campaign Registry) brand+campaign registration for the sender. |
IDEMPOTENCY_KEY_REQUIRED | varies | Audit #CB-010 — Idempotency-Key header is REQUIRED on money-moving mutations (top-up checkout, future similar endpoints) but the caller omitted it entirely. |
INVALID_IDEMPOTENCY_KEY | varies | API idempotency-key shape validation failures (length / charset). |
IDEMPOTENCY_KEY_REUSED | varies | Same Idempotency-Key replayed with a DIFFERENT request body. |
PAYMENT_IN_FLIGHT | 409 | TF-BILL-02 (2026-05-18) — tenant tried to cancel a crypto top-up intent that has progressed past waiting/confirming. |
EMPTY_AUDIENCE_NOT_ALLOWED | 422 | TF-SEG-04 (2026-05-18) — operator tried to create a campaign whose explicit audience selection (segment_id / list_id / explicit empty csv array) resolves to 0 recipients. |
BATCH_PRE_INSERT_FAILED | varies | Audit #BATCH-SMS-PERSIST-FAILED-1 — MessagesBatchService failed to bulk-pre-insert the recipients’ pending rows (Postgres unavailable / connection refused / unique-id collision). |
INTEGRATION_DISCONNECTED | 412 | Audit #INTEGRATION-WIRING-4 — campaign audience source references a CRM (HubSpot / Salesforce / Pipedrive) the tenant hasn’t connected via Nango. |
AUDIENCE_RESOLVE_FAILED | 502 | Audit #INTEGRATION-WIRING-4 — the resolver failed mid-iterate on a CRM-backed audience (Nango timeout, rate-limit, auth expired). |
NANGO_DESTINATION_FAILED | 502 | DECISION — CDP_NANGO_DESTINATIONS_2026_05_28 — outbound CDP activation through Nango Cloud’s triggerAction failed (HubSpot, Salesforce, Braze, Iterable, Customer.io, Klaviyo). |
Audit #WAVE-O-FOUNDATION — Orby
| Code | HTTP | Description |
|---|---|---|
ORBY_TOOL_FORBIDDEN | 403 | Orby tried to use a tool outside its permission set (403). |
ORBY_TOOL_NOT_AUTHORIZED | 403 | Audit #SCAN-AI-V3-010 (AGT-026 follow-up) — execute-time gate. |
ORBY_TOOL_CLAIM_MISSING | 401 | Audit #SCAN-AI-V3-010 — the session token decoded successfully but carries no tools claim (or a non-array claim). |
ORBY_SESSION_MALFORMED | 401 | The Orby session token is malformed (401). |
ORBY_SESSION_SIG_INVALID | 401 | The Orby session token’s signature didn’t verify (401). |
ORBY_SESSION_EXPIRED | 401 | The Orby session token is past its expiry (401). |
ORBY_SESSION_REVOKED | 401 | The Orby session token has been revoked (401). |
ORBY_SESSION_REQUIRED | 403 | Orby is operator-facing only; a request authenticated with an API key rather than a signed-in dashboard session is rejected. |
ORBY_RATE_LIMITED | 429 | Orby exceeded its per-session action rate limit (429). |
ORBY_THREAD_NOT_OWNED | 403 | The caller doesn’t own this Orby thread (403). |
ORBY_ITERATION_LIMIT | 422 | Orby hit its per-turn iteration limit (422). |
ORBY_COST_CAP_EXCEEDED | varies | Reserved Orby cost-cap error code. |
ORBY_LLM_FAILED | 502 | Orby’s LLM call failed upstream (502). |
ORBY_TURN_FAILED | 502 | An Orby turn couldn’t be completed (502). |
ORBY_KB_NOT_INDEXED_YET | 409 | The knowledge-base index for search isn’t ready yet—retry shortly (409). |
ORBY_KB_REINDEX_FAILED | varies | Reserved Orby knowledge-base error code. |
KB_VECTOR_DELETE_FAILED | 502 | AIAAS-KB-VECDB-CONSISTENCY (2026-06-02) — a KB document delete could not remove the document’s vectors from the Qdrant search index, so the kb_documents row was deliberately RETAINED (it is the only handle on the orphaned vectors that… |
Audit #AGT-001 — confirmation-gate redemption
| Code | HTTP | Description |
|---|---|---|
PENDING_ACTION_NOT_APPROVABLE | 409 | Pending agent action is not in pending state (already approved / cancelled). |
PENDING_ACTION_RACE | 409 | Pending agent action lost the approve/reject race against a concurrent operator. |
PENDING_ACTION_EXPIRED | 409 | Pending agent action was past its 5-min approval window (#UX-006). |
PENDING_ACTION_PERSIST_FAILED | 500 | The runtime could not persist a pending_agent_actions row, so the tool was refused (fail-closed). |
TOOL_EXECUTION_FAILED | 500 | Approved tool’s executor threw a non-DevotelError exception. |
Audit #AGT-007 — custom-tool dispatch body cap
| Code | HTTP | Description |
|---|---|---|
CUSTOM_TOOL_RESPONSE_TOO_LARGE | 502 | Custom-tool executor response body exceeded the 1 MB ceiling. |
Audit #AGT-002 — accurate cost cap
| Code | HTTP | Description |
|---|---|---|
COST_CAP_EXCEEDED | 402 | Per-conversation LLM cost cap exceeded (accurate computation via Anthropic rate table). |
Audit #WAVE-O-NEW-ENDPOINTS
| Code | HTTP | Description |
|---|---|---|
IVR_FLOW_GRAPH_INVALID | 422 | IVR flow graph failed structural validation (no entry, orphan edges, etc). |
CALL_TRANSCRIPT_UNAVAILABLE | 422 | POST /voice/calls/:id/summary — no usable transcript on the call. |
LLM_PROVIDER_UNCONFIGURED | 503 | No LLM provider key configured on the platform. |
CALL_SUMMARY_LLM_MALFORMED | 502 | LLM reply was malformed / unparseable after retries. |
VIDEO_TRANSCRIPT_UNAVAILABLE | 422 | UCaaS P1 (2026-05-30) — POST /video/sessions/:id/summary — no usable transcript on the meeting (no captions captured, transcript too short, etc.). |
VIDEO_SUMMARY_LLM_MALFORMED | 502 | UCaaS P1 (2026-05-30) — POST /video/sessions/:id/summary — LLM reply was malformed / unparseable after retries. |
VIDEO_SESSION_SUMMARY_FAILED | varies | UCaaS P1 (2026-05-30) — POST /video/sessions/:id/summary — unexpected route-level failure (DB / tenant client / sanitised envelope). |
VIDEO_LIVE_TRANSCRIPT_UNAVAILABLE | 422 | In-meeting live AI assistant (“ask AI during the meeting” / catch-me-up) — POST /video/rooms-scheduled/:id/ask — the LIVE-room counterpart to VIDEO_TRANSCRIPT_UNAVAILABLE: no usable transcript has been captured for the room yet (capt… |
VIDEO_LIVE_ASSIST_LLM_MALFORMED | 502 | POST /video/rooms-scheduled/:id/ask — LLM reply was malformed / unparseable after retries. |
PUSH_CATEGORY_IDENTIFIER_CONFLICT | 409 | Push category identifier collision with an existing tenant row. |
BROADCAST_TOO_LARGE | 422 | POST /push broadcast (user_ids: ["*"]) whose deliverable audience — enabled device tokens minus push/all suppression-list opt-outs — exceeds the MAX_BROADCAST_RECIPIENTS cap. |
SCHEDULED_PUSH_NOT_CANCELLABLE | 409 | 409 — DELETE /push/scheduled/:id refused to cancel a scheduled push that is no longer in scheduled state. |
SQUAD_DAILY_CAP_REACHED | 429 | Agent squad’s today-so-far LLM cost meets/exceeds its configured daily cap. |
CONVERSATION_COST_CAP_REACHED | 429 | Per-conversation LLM cost meets/exceeds the agent’s configured cap. |
AGENT_DAILY_SPEND_CAP_REACHED | 429 | SCAN-BILL-FRESH-001 / invariant #64 — tenant’s today-so-far agent LLM spend meets/exceeds the configured daily cap, thrown from the gateway-level callClaude / streamClaude / callClaudeStructured chokepoint. |
SAFETY_CLASSIFIER_REFUSAL | 403 | Audit #VOICE-FRAUD-9 — Anthropic’s server-side Constitutional Classifiers refused the completion (stop_reason:"refusal") AND DEVOTEL_ANTHROPIC_CONSTITUTIONAL_CLASSIFIERS_ENABLED is on, so the LLM gateway BLOCKS the refused content ra… |
VOICE_BIOMETRICS_TIMEOUT | 503 | Voice biometrics sidecar did not respond within the 10-second timeout. |
VOICE_BIOMETRICS_UNAVAILABLE | 503 | Voice biometrics sidecar is unreachable (network error). |
VOICE_BIOMETRICS_AUTH_FAILED | 500 | HMAC authentication with voice biometrics sidecar failed. |
VOICE_BIOMETRICS_ERROR | 503 | Voice biometrics sidecar returned an unexpected error. |
CONFIGURATION_ERROR | 500 | DEVOTEL_VOICE_BIOMETRICS_INTERNAL_SECRET env var missing. |
INVALID_AUDIO | 400 | Audio payload is invalid (not WAV, empty, or corrupt). |
AUDIO_TOO_LARGE | 400 | Audio clip exceeds the 30-second / 2MB maximum. |
SPOOF_DETECTED | 422 | Voice enrollment rejected — anti-spoof score is below threshold. |
DEEPFAKE_DETECTED | 422 | Voice enrollment rejected — Resemble Detect flagged the audio as a synthetic / cloned voice (deepfake score at or above threshold). |
DUPLICATE_VOICEPRINT | 409 | Identical voiceprint already enrolled for this contact. |
IVR_INTENT_CREATE_ERROR | 500 | IVR intent create failed (non-constraint DB error). |
IVR_CLASSIFY_LLM_ERROR | 503 | Conversational IVR NLU classifier — LLM call failed. |
IVR_SLOT_EXTRACT_LLM_ERROR | 503 | Conversational IVR slot-filling / entity extraction — LLM call failed. |
HIPAA_BAA_REQUIRED | varies | HIPAA BAA enforcement gate (SCAN-CSPAAS-004) — tenant is opted into HIPAA mode (settings.hipaa_required = true) but does not have an executed, in-term Business Associate Agreement on file. |
HIPAA_BAA_GATE_DB_FAIL | 500 | HIPAA BAA enforcement gate (SCAN-CSPAAS-004) — DB read failed while verifying BAA status. |
ESCALATION_EMAIL_RECIPIENTS_FORBIDDEN | 403 | Returned by PUT /settings/general when the operator attempts to persist settings.escalation.email_recipients containing one or more entries whose domain is not present in the org-member domain allowlist. |
ESCALATION_EMAIL_RECIPIENTS_INVALID | varies | Returned by PUT /settings/general when the operator submits settings.escalation.email_recipients as a non-array or with non-string entries. |
ESCALATION_EMAIL_RECIPIENTS_TOO_MANY | varies | Returned by PUT /settings/general when the operator submits a settings.escalation.email_recipients array of length > 20. |
ESCALATION_CHANNEL_FORBIDDEN | 403 | Returned by POST /agents and PUT /agents/:id when the operator sets an AI-agent escalation forward with delivery medium email (config.escalation_channel_type === "email") whose escalation_channel address is on a domain that does NO… |
ORG_COMPLIANCE_EMERGENCY_STOP | varies | CCaaS gap #51 — Org-wide compliance emergency stop (panic button). |
CDP_SOURCE_QUERY_MULTI_STATEMENT_FORBIDDEN | 400 | DECISION — CDPAAS_REVERSE_ETL_SQL_INJECTION_AND_MIGRATION_FIX_2026_05_28. |
AGENT_CHANNEL_CAP_EXCEEDED | 422 | CCaaS-PARITY (2026-05-30) — raised by the per-agent per-channel concurrency-cap routing gate (apps/api/src/lib/agent-channel-cap.ts) when an attempt to assign a conversation onto an agent would exceed the configured `agent_channel_caps… |
AGENT_INVOCATION_RATE_LIMIT_EXCEEDED | 429 | AIaaS per-agent invocation throttle (2026-06-29) — a single AI agent exceeded its operator-configured per-minute invocation rate (config.invocation_rate_limit_per_min). |
AGENT_INVOCATION_QUOTA_EXCEEDED | 429 | AIaaS per-agent invocation throttle (2026-06-29) — a single AI agent exhausted its operator-configured daily invocation quota (config.invocation_quota_per_day). |
AGENT_RATE_LIMIT_EXCEEDED | 429 | AIaaS per-agent configured rate-limit gate (2026-06-30) — a single AI agent exceeded one of its operator-configured invocation ceilings on the chat / stream entrypoint. |
OUTBOUND_CALLING_DISABLED | 409 | POST /agents/:id/calls (agent-originated outbound voice call, outbound-call.service.ts) refused with 409 because the operator has not enabled the agent’s outbound-calling toggle. |
INTERNAL | 500 | A backwards-compatible alias of INTERNAL_ERROR (500). |
BAD_GATEWAY | 502 | An upstream provider returned an error (502). |
UPSTREAM_ERROR | 502 | An upstream provider returned an error (502). |
RATE_LIMITED | 429 | Global HTTP 429 rate limit from the platform-wide request limiter, carrying a retry_after value in seconds and a matching Retry-After header. |
UNAUTHENTICATED | 401 | No valid session / token (401). |
UNPROCESSABLE_ENTITY | 422 | The request payload failed validation (422). |
INVARIANT_VIOLATION | 500 | A server-side invariant check failed—treat as 500-class and retry with request_id (500). |
SESSION_REVOKED | 401 | The session has been revoked—re-authenticate (401). |
LIGHT_SEAT_RESTRICTED | 403 | A light-seat user attempted an action outside their restricted permission set (403). |
INVALID_TOKEN | 401 | The token is malformed or not recognized (401). |
INVALID_CODE | 422 | The provided code (e.g. 2FA, data-deletion confirm) isn’t valid (422). |
CHALLENGE_INVALID | 422 | The voice-biometrics challenge response didn’t match (422). |
ALREADY_VERIFIED | 409 | This resource was already verified—no further action needed (409). |
VERIFICATION_REJECTED | 422 | Verification was rejected (e.g. KYC, caller-ID, 10DLC) (422). |
INVALID_FILTER | 422 | The filter parameter isn’t valid (422). |
INVALID_STATUS | 422 | The requested status value isn’t in the allowed set (422). |
INVALID_PRIORITY | 422 | The priority value isn’t in the allowed set (422). |
INVALID_STATE_TRANSITION | varies | The entity’s current status doesn’t allow this transition (422 or 409). |
INVALID_TRANSITION | 422 | The requested state transition isn’t permitted (422). |
INVALID_LAUNCH_STATE | 422 | RCS launches require the agent to be in a valid launch state—retry with the correct pre-launch state (422). |
INVALID_PHONE | 422 | The phone number isn’t valid for this endpoint (422). |
INVALID_CALLER_ID | 422 | The caller-ID value isn’t valid (422). |
INVALID_DESTINATION_URL | 422 | The destination URL failed validation (422). |
INSUFFICIENT_FUNDS | 402 | The balance is too low to cover this (402). |
MAX_PRICE_EXCEEDED | 422 | The max-price guard rejected the send—the quoted price exceeds your cap (422). |
COUNTRY_NOT_ALLOWED | 422 | The destination country is blocked by the tenant’s country allowlist (422). |
CHANNEL_COUNTRY_BLOCKED | 422 | This channel is blocked for the destination country by tenant policy (422). |
NETWORK_ACCESS_DENIED | 403 | 403 — a NaaS Connectivity (eSIM/IoT) data session’s reported country/carrier is denied by its fleet’s Network Access Profile (CONNECTIVITY-06: Telnyx Wireless Blocklists / Twilio Super SIM Network Access Profile parity). |
CHANNEL_FAILED | 502 | The channel-level operation failed (502). |
BINDING_REQUIRED | 422 | The verification binding (e.g. device bind) is required first (422). |
BINDING_MISMATCH | 422 | The binding (device / token) doesn’t match the expected one (422). |
AGENT_NOT_FOUND | 404 | The requested agent doesn’t exist (404). |
AGENT_ARCHIVED | 422 | The agent is archived—restore it before mutating it (422). |
AGENT_REQUIRED | 422 | This route requires an agent context that the request didn’t provide (422). |
AGENT_REFERENCED_BY_CHILD_ROWS | 409 | The agent can’t be deleted—child rows reference it (409). |
AGENT_REFERENCED_BY_SQUAD | 409 | The agent can’t be deleted—a squad still references it (409). |
NO_AGENT_AVAILABLE | 409 | No agent (human or AI) is available to take this (409). |
UNKNOWN_SKILL | 422 | The requested voice skill isn’t registered (422). |
SKILL_CATALOG_ORG_MISSING | 422 | The skill-catalog operation requires an org context (422). |
SKILL_SLUG_ALREADY_EXISTS | 409 | A skill with this slug already exists—pick a unique slug (409). |
TOOL_IMPL_NOT_REGISTERED | 500 | The Orby tool’s handler isn’t registered in the runtime (500). |
INVALID_HANDOFF_TARGETS | 422 | The handoff target list is invalid (422). |
INVALID_KNOWLEDGE_BASE_IDS | 422 | One or more knowledge-base IDs don’t resolve to existing KBs (422). |
LEGACY_KB_DOC_NOT_RECHUNKABLE | 422 | A legacy KB document can’t be re-chunked into the modern embeddings model (422). |
POLICY_SCANNER_UNAVAILABLE | 503 | The content-policy scanner isn’t reachable (503). |
CUSTOM_CODE_FORBIDDEN | 403 | Custom code isn’t allowed in this surface (403). |
QUEUE_NOT_FOUND | 404 | The requested queue doesn’t exist (404). |
QUEUE_DEPTH_CAP_REACHED | 503 | The queue has reached its depth cap—the call will route to overflow (503). |
QUEUE_ENTRY_NOT_QUEUED | 409 | This operation needs the entry to still be queued—it was already dequeued (409). |
OVERFLOW_QUEUE_NOT_FOUND | 404 | The configured overflow queue doesn’t exist (404). |
OVERFLOW_QUEUE_CHAIN_CYCLE | 422 | The overflow chain forms a cycle—fix the chain before saving (422). |
OVERFLOW_QUEUE_SELF_CHAIN | 422 | The overflow chain points back to the same queue (422). |
SKIP_INELIGIBLE | 422 | The call isn’t eligible for skip (e.g. already answered) (422). |
NOT_IN_WRAPUP | 409 | The call isn’t in wrap-up, so extend / end-wrapup isn’t applicable (409). |
DISPOSITION_REQUIRED | 422 | A disposition must be recorded for this call before the agent is freed (422). |
DISPOSITION_CODE_NOT_FOUND | 404 | The disposition code isn’t registered (404). |
DISPOSITION_TAG_REQUIRED | 422 | A disposition tag is required for this queue (422). |
DISPOSITION_TAG_NOT_FOUND | 404 | The disposition tag isn’t registered (404). |
AUX_CODE_ALREADY_EXISTS | 409 | An AUX (availability) code with this name exists—pick a unique name (409). |
AUX_CODE_UPDATE_EMPTY | 422 | The AUX update payload is empty—include at least one field (422). |
TEMPLATE_NOT_FOUND | 404 | The requested template doesn’t exist (404). |
TEMPLATE_NAME_CONFLICT | 409 | A template with this name already exists (409). |
TEMPLATE_LIMIT_EXCEEDED | 422 | The tenant is at the template limit (422). |
SPEECH_CATEGORY_NOT_FOUND | 404 | The speech-analytics category doesn’t exist (404). |
SPEECH_CATEGORY_SLUG_CONFLICT | 409 | A speech category with this slug already exists (409). |
RECORDING_CONSENT_ACKNOWLEDGEMENT_REQUIRED | 422 | Recording consent must be acknowledged before this party joins (422). |
EMERGENCY_DESTINATION_REQUIRED | 422 | An emergency-services destination must be set before enabling outbound calling on this number (422). |
VOICE_OVERLOAD_CHAT_VETOED | 422 | The system blocked this chat→voice escalation while voice is overloaded (422). |
CAMPAIGN_NOT_DRAFT | 422 | This action needs the campaign to be in draft state (422). |
CAMPAIGN_NOT_PENDING_APPROVAL | 422 | This action needs the campaign in pending-approval state (422). |
CONVERSATION_LINK_EXISTS | 409 | The conversation is already linked—no action needed (409). |
BLOCKED_NUMBER_ALREADY_EXISTS | 409 | The number is already on the blocked list (409). |
OPT_OUT_LIST_LABEL_CONFLICT | 409 | An opt-out list with this label exists—pick a unique label (409). |
SEND_WINDOW_LOOKUP_FAILED | 502 | The tenant send-window lookup failed—retry with backoff (502). |
MESSAGING_SERVICE_NOT_FOUND | 404 | The Messaging Service doesn’t exist (404). |
MESSAGING_SERVICE_EXTERNAL_ID_CONFLICT | 409 | The external id on a Messaging Service is already used (409). |
MESSAGING_SERVICE_RESOLUTION_FAILED | 502 | The Messaging Service resolution failed—check the service mapping (502). |
MESSENGER_TAG_REQUIRED | 422 | Messenger requires a message tag when outside the 24-hour window (422). |
MESSENGER_OUTSIDE_24H_WINDOW | 422 | Messenger only allows free-form send inside the 24-hour window (422). |
CARRIER_PORT_OUT_FAILED | 502 | The carrier-side port-out rejected (e.g. incorrect account info) (502). |
PORT_OUT_NOT_SUPPORTED | 422 | Port-out isn’t supported on this number (422). |
PORT_OUT_PIN_MISMATCH | 422 | The port-out PIN doesn’t match (422). |
MEF_INTEGRATION_PENDING | varies | DEPRECATED — no longer emitted; country-rule sync now fails open, returning HTTP 200 with any sync issues reported in the response’s errors[] array instead of throwing this former HTTP 501 code. |
VERIFY_LINE_TYPE_BLOCKED | 422 | The line-type check blocked this (e.g. non-voice-capable) (422). |
VERIFY_EMAIL_UNDELIVERABLE | 422 | 422 — an email OTP (channel: email | magic_link) was requested for an address whose domain PROVABLY cannot receive mail: the domain publishes no MX record (NXDOMAIN / ENODATA). |
QR_RENDER_FAILED | 502 | The QR code couldn’t be rendered (502). |
VIDEO_ROOM_LOCKED | 403 | The video room is locked—join is refused (403). |
VIDEO_PARTICIPANT_BANNED | 403 | The participant is banned from this room (403). |
VIDEO_CANNOT_MODERATE_ROOM_HOST | 403 | 403 — a moderator (owner/admin/developer per the tenant-wide writeGuard) attempted a moderation VERB (kick / mute / unmute / ban) against the room’s creator (video_rooms.host_user_id), who is immune as a TARGET in their own room. |
VIDEO_MUTE_ALL_FAILED | 502 | The mute-all operation failed (502). |
VIDEO_BROADCAST_FAILED | 502 | Starting the broadcast failed (502). |
VIDEO_BREAKOUT_CREATE_FAILED | 502 | Creating the breakout room failed (502). |
VIDEO_BREAKOUT_MOVE_FAILED | 502 | Moving a participant into the breakout failed (502). |
VIDEO_WAITING_ROOM_FAILED | 502 | The waiting-room operation failed (502). |
VIDEO_WAITING_ROOM_ADMIT_FAILED | 502 | Admitting a participant from the waiting room failed (502). |
VIDEO_PARTICIPANT_TIER_UPDATE_FAILED | 502 | Updating the participant’s tier (e.g. host / viewer) failed (502). |
VIDEO_INGRESS_CREATE_FAILED | 502 | Creating the video ingress endpoint failed (502). |
VIDEO_RTMP_EGRESS_START_FAILED | 502 | Starting the RTMP egress failed (502). |
VIDEO_RECORDING_BACKEND_UNAVAILABLE | 503 | The video recording backend isn’t reachable (503). |
Video / Rooms (server-side fallback codes)
| Code | HTTP | Description |
|---|---|---|
VIDEO_ROOM_CREATION_FAILED | 500 | 500 — an unexpected server error prevented the request to create the video room; the operation did not complete, so retry the request. |
VIDEO_ROOM_LIMITS_FAILED | 500 | 500 — an unexpected server error prevented the request to load your video room limits; the operation did not complete, so retry the request. |
VIDEO_ROOM_LIST_FAILED | 500 | 500 — an unexpected server error prevented the request to load your video rooms; the operation did not complete, so retry the request. |
VIDEO_ROOM_FETCH_FAILED | 500 | 500 — an unexpected server error prevented the request to load this video room; the operation did not complete, so retry the request. |
VIDEO_ROOM_JOIN_FAILED | 500 | 500 — an unexpected server error prevented the request to generate a join token for this room; the operation did not complete, so retry the request. |
VIDEO_ROOM_END_FAILED | 500 | 500 — an unexpected server error prevented the request to end this video room; the operation did not complete, so retry the request. |
VIDEO_RECORDING_START_FAILED | 500 | 500 — an unexpected server error prevented the request to start the recording; the operation did not complete, so retry the request. |
VIDEO_RECORDING_STOP_FAILED | 500 | 500 — an unexpected server error prevented the request to stop the recording; the operation did not complete, so retry the request. |
VIDEO_RTMP_EGRESS_STOP_FAILED | 500 | 500 — an unexpected server error prevented the request to stop the live stream; the operation did not complete, so retry the request. |
VIDEO_LIVESTREAM_PLAYBACK_FAILED | 500 | 500 — an unexpected server error prevented the request to load the live stream playback URL; the operation did not complete, so retry the request. |
VIDEO_INGRESS_DELETE_FAILED | 500 | 500 — an unexpected server error prevented the request to remove the ingress; the operation did not complete, so retry the request. |
VIDEO_INGRESS_LIST_FAILED | 500 | 500 — an unexpected server error prevented the request to load active ingresses; the operation did not complete, so retry the request. |
VIDEO_INVITE_CREATE_FAILED | 500 | 500 — an unexpected server error prevented the request to create a guest invite; the operation did not complete, so retry the request. |
VIDEO_INVITE_LIST_FAILED | 500 | 500 — an unexpected server error prevented the request to load guest invites for this room; the operation did not complete, so retry the request. |
VIDEO_INVITE_REVOKE_FAILED | 500 | 500 — an unexpected server error prevented the request to revoke this guest invite; the operation did not complete, so retry the request. |
VIDEO_PARTICIPANT_UNMUTE_FAILED | 500 | 500 — an unexpected server error prevented the request to unmute this participant; the operation did not complete, so retry the request. |
VIDEO_PARTICIPANT_BAN_FAILED | 500 | 500 — an unexpected server error prevented the request to ban this participant; the operation did not complete, so retry the request. |
VIDEO_PARTICIPANT_UNBAN_FAILED | 500 | 500 — an unexpected server error prevented the request to lift the ban for this participant; the operation did not complete, so retry the request. |
VIDEO_BAN_LIST_FAILED | 500 | 500 — an unexpected server error prevented the request to load the banned participants for this room; the operation did not complete, so retry the request. |
VIDEO_RECORDING_CONSENT_SET_FAILED | 500 | 500 — an unexpected server error prevented the request to update this participant’s recording consent; the operation did not complete, so retry the request. |
VIDEO_RECORDING_CONSENT_LIST_FAILED | 500 | 500 — an unexpected server error prevented the request to load the recording-consent list for this room; the operation did not complete, so retry the request. |
VIDEO_ROOM_LOCK_FAILED | 500 | 500 — an unexpected server error prevented the request to change the room lock state; the operation did not complete, so retry the request. |
VIDEO_PARTICIPANT_ROLE_UPDATE_FAILED | 500 | 500 — an unexpected server error prevented the request to change this participant’s role; the operation did not complete, so retry the request. |
VIDEO_PARTICIPANT_RECEIVE_CONTROL_FAILED | 500 | 500 — an unexpected server error prevented the request to update this participant’s receive settings; the operation did not complete, so retry the request. |
VIDEO_SPOTLIGHT_SET_FAILED | 500 | 500 — an unexpected server error prevented the request to spotlight this participant; the operation did not complete, so retry the request. |
VIDEO_SPOTLIGHT_CLEAR_FAILED | 500 | 500 — an unexpected server error prevented the request to clear the spotlight; the operation did not complete, so retry the request. |
VIDEO_ROOM_SSO_POLICY_FAILED | 500 | 500 — an unexpected server error prevented the request to update the room’s single sign-on access policy; the operation did not complete, so retry the request. |
VIDEO_INVITE_REDEEM_FAILED | 500 | 500 — an unexpected server error prevented the request to redeem this guest invite; the operation did not complete, so retry the request. |
VIDEO_HAND_RAISE_FAILED | 500 | 500 — an unexpected server error prevented the request to raise your hand; the operation did not complete, so retry the request. |
VIDEO_HAND_LOWER_FAILED | 500 | 500 — an unexpected server error prevented the request to lower your hand; the operation did not complete, so retry the request. |
VIDEO_HAND_QUEUE_LIST_FAILED | 500 | 500 — an unexpected server error prevented the request to load the raised-hand queue; the operation did not complete, so retry the request. |
VIDEO_HAND_QUEUE_CLEAR_FAILED | 500 | 500 — an unexpected server error prevented the request to clear the raised-hand queue; the operation did not complete, so retry the request. |
VIDEO_REACTION_FAILED | 500 | 500 — an unexpected server error prevented the request to send your reaction; the operation did not complete, so retry the request. |
VIDEO_POLL_LAUNCH_FAILED | 500 | 500 — an unexpected server error prevented the request to launch the poll; the operation did not complete, so retry the request. |
VIDEO_POLL_VOTE_FAILED | 500 | 500 — an unexpected server error prevented the request to record your vote; the operation did not complete, so retry the request. |
VIDEO_POLL_CLOSE_FAILED | 500 | 500 — an unexpected server error prevented the request to close the poll; the operation did not complete, so retry the request. |
VIDEO_QA_ASK_FAILED | 500 | 500 — an unexpected server error prevented the request to submit your question; the operation did not complete, so retry the request. |
VIDEO_QA_ANSWER_FAILED | 500 | 500 — an unexpected server error prevented the request to post the answer to this question; the operation did not complete, so retry the request. |
VIDEO_WHITEBOARD_OPEN_FAILED | 500 | 500 — an unexpected server error prevented the request to open the whiteboard; the operation did not complete, so retry the request. |
VIDEO_WHITEBOARD_DRAW_FAILED | 500 | 500 — an unexpected server error prevented the request to sync your drawing to the room; the operation did not complete, so retry the request. |
VIDEO_WHITEBOARD_CLEAR_FAILED | 500 | 500 — an unexpected server error prevented the request to clear the whiteboard; the operation did not complete, so retry the request. |
VIDEO_WHITEBOARD_CLOSE_FAILED | 500 | 500 — an unexpected server error prevented the request to close the whiteboard; the operation did not complete, so retry the request. |
VIDEO_CHAT_SEND_FAILED | 500 | 500 — an unexpected server error prevented the request to send your message; the operation did not complete, so retry the request. |
VIDEO_CHAT_DELETE_FAILED | 500 | 500 — an unexpected server error prevented the request to remove that message; the operation did not complete, so retry the request. |
VIDEO_CHAT_CLEAR_FAILED | 500 | 500 — an unexpected server error prevented the request to clear the chat; the operation did not complete, so retry the request. |
VIDEO_CHAT_BLOCKED_BY_POLICY | 422 | 422 — the chat message was rejected by the room’s proactive content-safety policy (mode=block); it was neither delivered to other participants nor persisted. |
VIDEO_CHAT_POLICY_UNAVAILABLE | 503 | 503 — the chat message could not be screened because the room’s content-safety policy read failed (transient DB error); the client should retry rather than fall through to an unscreened broadcast. |
VIDEO_ROOM_CHAT_POLICY_FAILED | 502 | 502 — an unexpected server error prevented persisting the room’s chat content-safety policy; the change did not take effect, so retry the request. |
VIDEO_DIAL_IN_FAILED | 500 | 500 — an unexpected server error prevented the request to load the dial-in details for this room; the operation did not complete, so retry the request. |
VIDEO_TRANSCRIPT_LIST_FAILED | 500 | 500 — an unexpected server error prevented the request to load the transcript for this room; the operation did not complete, so retry the request. |
VIDEO_TRANSCRIPT_DELETE_FAILED | 500 | 500 — an unexpected server error prevented the request to delete the transcript for this room; the operation did not complete, so retry the request. |
VIDEO_TRANSCRIPT_EXPORT_FAILED | 500 | 500 — an unexpected server error prevented the request to export the caption file for this room; the operation did not complete, so retry the request. |
VIDEO_TRANSCRIPT_CORRECT_FAILED | 500 | 500 — an unexpected server error prevented the request to apply that caption correction; the operation did not complete, so retry the request. |
VIDEO_LIVE_ASK_FAILED | 500 | 500 — an unexpected server error prevented the in-meeting AI assistant from answering; the operation did not complete, so retry the request. |
WEBHOOK_HANDLING_FAILED | 500 | 500 — an unexpected error prevented processing the inbound video webhook; the event was not handled and may be retried by the provider. |
Numbers / Provisioning
| Code | HTTP | Description |
|---|---|---|
CARRIER_RATE_LIMITED | 429 | 429 — bulk-number-purchase per-item result code: the carrier rate-limited the batch, so the remaining items were skipped without a carrier call; retry the failed rows in a few seconds. |
LOA_NOT_SIGNED | 409 | 409 — hosted-messaging order submit refused because the LOA has not been signed yet (loa_signed_at is null); sign the LOA before submitting the order to the hosting carrier. |
LOA_URL_INVALID_ORIGIN | 422 | 422 — number-porting create rejected loaFileUrl because it is not a Devotel-issued GCS signed URL (wrong host or path prefix); upload the LoA via POST /files/upload and submit the returned url. |
TFV_LINT_BLOCKED | 422 | 422 — toll-free-verification submit blocked by the pre-submit content lint: one or more sample messages match a disallowed-vertical pattern at error severity; fix the issues in details.lint before resubmitting. |
UNSUPPORTED_MEDIA_TYPE | 415 | 415 — number-document upload rejected because the request body was not multipart/form-data; re-send the document as a multipart file part. |
Compliance (DSAR / RMD / Residency / Traceback)
| Code | HTTP | Description |
|---|---|---|
DSAR_CREATE_FAILED | 500 | 500 — POST /compliance/dsar failed to persist the new GDPR/CCPA data-subject-access request because the DB insert threw; the operation is retry-safe. |
VERIFICATION_NOT_PENDING | 409 | 409 — the DSAR identity-verification step was actioned but the request has no pending verification: it may already have been verified, rejected, or filed under a regime that does not require verification. |
DSAR_CANCEL_FAILED | 500 | 500 — POST /compliance/dsar/:id/cancel failed to withdraw the request because the DB update threw; the operation is retry-safe. |
DSAR_NOT_CANCELLABLE | 409 | 409 — the DSAR cannot be withdrawn because it does not exist or has already completed, failed, expired, or been cancelled. |
DSAR_BUCKET_UNCONFIGURED | 500 | 500 — the decrypted Article-15 export download could not run because no DSAR export storage location is configured on this deployment. |
DSAR_DECRYPT_LIST_FAILED | 502 | 502 — GET /compliance/dsar/:id/decrypted could not list the export’s encrypted files because the storage backend was unreachable; retry-safe. |
DSAR_DECRYPT_NO_ARTIFACTS | 404 | 404 — no encrypted export files were found for this DSAR; the export has likely expired and been removed. |
DSAR_DECRYPT_DOWNLOAD_FAILED | 502 | 502 — an individual encrypted export file failed to download while assembling the decrypted bundle; retry-safe. |
DSAR_DECRYPT_FAILED | 500 | 500 — an export file could not be decrypted, because the encryption key has rotated since the export was generated or the stored file is corrupt. |
DSAR_DECRYPT_TOO_LARGE | 413 | 413 — the combined decrypted bundle is too large to assemble in one response; download the per-file signed URLs individually instead. |
RMD_NOT_FOUND | 404 | 404 — no Robocall Mitigation Database (RMD) registration exists for this organization; create one with POST /compliance/rmd first. |
RMD_INVALID_TRANSITION | 409 | 409 — the requested RMD lifecycle action (submit / certify / remediate / resolve / withdraw) is not allowed from the registration’s current status. |
RMD_LOCKED | 409 | 409 — the RMD registration is in a submitted/certified state and cannot be edited in place; use the lifecycle endpoints (submit / certify / remediation / resolve / withdraw) instead. |
RMD_INCOMPLETE | 409 | 409 — the RMD registration cannot be submitted yet because required fields are missing; error.details lists the outstanding blockers. |
RESIDENCY_LOCKED | 409 | 409 — PUT /compliance/data-residency refused to change the pinned residency region because the current configuration is locked and the transition is not permitted. |
RESIDENCY_NOT_ENFORCED | 409 | 409 — the residency region must be pinned and enforced (PUT /compliance/data-residency with enforced=true) before it can be locked. |
RESIDENCY_REGION_UNAVAILABLE | 409 | 409 — PUT /compliance/data-residency refused to enforce the requested residency region because that region’s storage boundary is not provisioned on the platform (not in the available-regions allowlist); the region may be pinned advisory-… |
ARCHIVAL_CHANNEL_NOT_ENABLED | 409 | 409 — POST /compliance/archival/export refused the run because the requested channel is not enabled in the tenant’s archival policy (or archival is not enabled at all); enable it via PUT /compliance/archival first. |
RESIDENCY_NOT_FOUND | 404 | 404 — no data-residency configuration exists for this organization. |
TRACEBACK_NOT_FOUND | 404 | 404 — no ITG (Industry Traceback Group) traceback case with that id exists for this organization. |
TRACEBACK_INVALID_TRANSITION | 409 | 409 — the requested traceback case transition (e.g. acknowledge) is not allowed from the case’s current status. |
BYOK_KEY_UNAVAILABLE | 409 | 409 — a BYOK-enforced tenant field-encryption call (encrypt or decrypt) refused to proceed because the organization’s customer-managed key is revoked, not fully provisioned (no wrapped data-encryption key yet), or its wrapped DEK could n… |
Campaigns / Journeys
| Code | HTTP | Description |
|---|---|---|
AUDIENCE_TOO_LARGE | 400 | 400 — campaign send/schedule refused because the resolved audience size exceeds the per-tenant maximum recipient count from resolveCampaignAudienceLimit; split the campaign or use a smaller audience. |
ALREADY_ENROLLED | 409 | 409 — single-contact journey enrollment refused because the contact already has an active enrollment row for this journey campaign. |
CAMPAIGN_NOT_RUNNING | 400 | 400 — single-contact enrollment refused because the target campaign is not in the running state. |
CAMPAIGN_TYPE_NOT_ENROLLABLE | 400 | 400 — single-contact enrollment is only supported for journey-type campaigns; the target campaign is a different type. |
JOURNEY_VALIDATION_FAILED | 400 | 400 — SCAN-CMP-013 journey resume re-validation failed: the stored journey_definition no longer passes graph validation. |
JOURNEY_DEFINITION_INVALID | 400 | 400 — the campaign steps payload is not a valid journey graph (nodes/edges missing or unparseable). |
JOURNEY_TRIGGER_MISSING | 400 | 400 — the journey graph has no journeyTrigger node, so no entry point can be resolved for enrollment. |
JOURNEY_TRIGGER_DISCONNECTED | 400 | 400 — the journey trigger node has no outgoing edge, so there is no first step to enroll a contact into. |
RESUME_FAILED | 500 | 500 — the journey executor threw while resuming a paused journey; the scheduler increments resume_attempts and backs off / DLQs. |
Contacts / Imports
| Code | HTTP | Description |
|---|---|---|
IMPORT_NOT_CANCELLABLE | 409 | 409 — POST /imports/:jobId/cancel (service cancelImport) refused: the import job exists but is already in a terminal state (succeeded / failed / cancelled), or it lost the concurrent cancel CAS race. |
IMPORT_STILL_RUNNING | 409 | 409 — POST /imports/:jobId/rollback (service rollbackImport) refused: the import is still pending / running, so it cannot be rolled back yet. |
ALREADY_ROLLED_BACK | 409 | 409 — POST /imports/:jobId/rollback refused: the import job already carries a rollback_at stamp, so the contacts it created were already deleted. |
IMPORT_JOB_NOT_FOUND | 404 | 404 — GET /contacts/imports/:id status poll found no import job with the supplied id in the caller’s tenant schema. |
IMPORT_QUEUE_UNAVAILABLE | 503 | 503 — async contact-import enqueue (POST /contacts/imports) could not hand the job to the import queue (queue / Redis offline). |
ERASURE_COOLING_OFF_ACTIVE | 409 | 409 — POST /contacts/:id/gdpr/erasure-request refused because a pending or executing Article-17 erasure request already exists for the contact (partial unique index uq_gdpr_erasure_requests_active). |
AI_INVALID_RULES | 422 | 422 — POST /contacts/segments/suggest-rules (AI natural-language segment builder) refused the LLM-proposed filter rules. |
CDP public ingest (signed-HMAC track API)
| Code | HTTP | Description |
|---|---|---|
CDP_UNAUTHORIZED | 401 | 401 — the public, signed-HMAC CDP ingest endpoint (POST /cdp/v1/{ingest_id}/{track|identify|page|screen|group|alias|batch}) rejected the request because HMAC signature, timestamp, or replay-nonce verification failed. |
CDP_PAYLOAD_TOO_LARGE | 413 | 413 — an inbound CDP ingest payload exceeded the inline byte cap (CDP_INLINE_MAX_BYTES, 1 MB). |
EVENT_PAYLOAD_TOO_LARGE | 413 | 413 — the developer event-ingestion surface refused an event whose properties JSON exceeds the ~1 MiB inline ceiling (CDP_INLINE_MAX_BYTES, 1024×1024 bytes). |
SCHEMA_VALIDATION_FAILED | 422 | 422 — a CDP ingest event failed strict-mode JSON-schema validation (the tenant runs event-schema enforcement in strict mode). |
TRACKING_PLAN_VIOLATION | 422 | 422 — a CDP ingest event was rejected by the tenant’s tracking plan running with enforcement=strict. |
Co-browse (live screen-share)
| Code | HTTP | Description |
|---|---|---|
COBROWSE_NOT_ACTIVE | 404 | 404 — an agent (POST /cobrowse/:conversationId/join, see apps/api/src/routes/cobrowse/cobrowse.controller.ts) or a visitor (apps/api/src/routes/widget/index.ts) asked to join a co-browse room for a conversation that has no live, jo… |
COBROWSE_UNAVAILABLE | 503 | 503 — the co-browse session was joinable, but the co-browse media token could not be minted because the media service is unreachable or not configured on this deployment. |
USER_ALREADY_IN_ORG | 409 | 409 — a Settings → Team invite was declined because the invited email already belongs to an organization; Orbit seats each user in a single organization, so that person can’t accept the invite — ask them to use a different email address. |
ALREADY_ORG_MEMBER | 409 | 409 — a Settings → Team invite was declined because the invited email is already a member of your own workspace, so no action is needed — manage that person from the members list instead of re-inviting them. |
CDP_SOURCE_KIND_NOT_YET_AVAILABLE | 400 | 400 — CDP reverse-ETL POST /cdp/sources rejected a kind that the warehouse source catalog (warehouse-source-catalog.catalog.ts) already lists for discovery, and that cdpSourceKindSchema (cdp.schemas.ts) already accepts, but whose… |
Handling Errors in Code
Node.js — retrying 429 with backoff
import { Orbit, OrbitApiError } from '@devotel-orbit/node'
const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY })
// Branch on error.code first — only some failures are retriable.
function classify(error: OrbitApiError) {
// 429 / 5xx are retriable; 4xx terminal errors (INVALID_PHONE_NUMBER,
// REAUTH_REQUIRED, ...) must not be re-sent.
const retriable = error.status >= 500 || error.status === 429
const retryAfterMs = error.details?.retry_after // seconds
return { retriable, retryAfterMs: retryAfterMs ? retryAfterMs * 1000 : 0 }
}
for (let attempt = 0; attempt < 3; attempt++) {
try {
await orbit.messages.send({ channel: 'sms', to: '+14155552671', body: 'Hi' })
break
} catch (error) {
if (error instanceof OrbitApiError) {
const { retriable, retryAfterMs } = classify(error)
// Log the envelope fields up front — request_id goes into the
// structured log so a support ticket can pinpoint the failure.
console.error({
code: error.code,
status: error.status,
requestId: error.requestId,
attempt,
})
if (!retriable) throw error
// Honor Retry-After when the server says so; otherwise
// exponential backoff.
await new Promise((resolve) =>
setTimeout(resolve, retryAfterMs || 2 ** attempt * 1000)
)
} else {
throw error
}
}
}
Python
import logging
import os
import time
from orbit_sdk import OrbitClient, OrbitError
logger = logging.getLogger(__name__)
client = OrbitClient(api_key=os.environ["ORBIT_API_KEY"])
for attempt in range(3):
try:
client.messages.send_sms(to="+14155552671", body="Hi")
break
except OrbitError as e:
# Capture the envelope fields in the structured log entry now —
# request_id is the key support uses to trace the request.
logger.error(
"orbit.send_sms failed",
extra={
"code": e.code,
"status": e.status,
"request_id": e.request_id,
"attempt": attempt,
},
)
# 429 / 5xx are retriable; 4xx terminal errors (e.g.
# INVALID_PHONE_NUMBER) must not be re-sent.
retriable = e.status is not None and (e.status >= 500 or e.status == 429)
if not retriable:
raise
# Honor the SDK's parsed retry_after (seconds) when present;
# otherwise exponential backoff.
retry_after = getattr(e, "retry_after", None)
time.sleep(retry_after if retry_after is not None else 2 ** attempt)
OrbitError per HTTP class —
OrbitAuthenticationError (401/403), OrbitRateLimitError (429,
carries retry_after), OrbitClientError (other 4xx), and
OrbitServerError (5xx) — so catch a subclass when you want the
specific class. OrbitError itself catches every API failure.
Go
import (
"errors"
"log"
"github.com/devotel/orbit-go/orbit"
)
client, _ := orbit.NewClient(os.Getenv("ORBIT_API_KEY"))
for attempt := 0; attempt < 3; attempt++ {
_, err := client.Messages().SendSMS(ctx, orbit.SendSMSInput{
To: "+14155552671", Body: "Hi",
})
var apiErr *orbit.Error
if !errors.As(err, &apiErr) {
break
}
// Log the envelope fields before deciding — RequestID is the key
// support uses to trace the request.
log.Printf("kind=%s code=%s status=%d request=%s attempt=%d",
apiErr.Kind, apiErr.Code, apiErr.StatusCode,
apiErr.RequestID, attempt)
// KindServer / KindRateLimit are retriable; KindClient /
// KindAuthentication are terminal.
retriable := apiErr.Kind == orbit.KindServer ||
apiErr.Kind == orbit.KindRateLimit
if !retriable {
break
}
// Honor the server-supplied RetryAfter when present; otherwise
// exponential backoff.
if apiErr.RetryAfter > 0 {
time.Sleep(apiErr.RetryAfter)
} else {
time.Sleep(time.Duration(1<<attempt) * time.Second)
}
}
Java, C#, PHP, Ruby
The Java, C#, PHP, and Ruby SDKs are pre-publish — not yet on Maven Central, NuGet, Packagist, or RubyGems, so the registry blocks on the per-language SDK pages describe the future registry shape. Vendor the source from the monorepo (sdk-java, sdk-csharp, sdk-php,
sdk-ruby) and use the SDK anyway: its escape-hatch client call —
client.request (Java and Ruby), client.RequestAsync (C#), or
$client->request (PHP) — covers the whole API surface, errors
included. Each language page walks the same model end to end. An
Orbit-originated OrbitError carries error_code /
status_code / request_id; the escape hatch throws the same
typed error subclasses as the typed methods, so the catch taxonomy
matches every other sample on this page.
client.request("POST", "/messages",
null,
Map.of("channel", "sms", "to", "+14155552671", "body", "Hi"),
null);
await client.RequestAsync("POST", "/messages",
body: new { channel = "sms", to = "+14155552671", body = "Hi" });
$client->request('POST', '/messages', null, [
'channel' => 'sms',
'to' => '+14155552671',
'body' => 'Hi',
]);
client.request("POST", "/messages",
body: { channel: "sms", to: "+14155552671", body: "Hi" })
The
Devotel and DevotelApiError aliases are exported for
compatibility with early-access code but Orbit / OrbitApiError
are the canonical names in @devotel-orbit/node v0.2+.A first-party Python SDK is on the roadmap but not yet shipped. Until
then, decode the JSON response body and read
meta.request_id /
error.code / error.message directly from any HTTP client.Always include the
request_id from the error response meta
when contacting Orbit support. This allows us to trace the exact
request through our infrastructure.