Troubleshooting
Start here when something does not work as expected. Each section describes a symptom, how to confirm the cause and how to fix it. For a specific error code, open its page from the error-code reference.
Three habits make every problem faster to solve:
- Read
error.code, not the HTTP status alone and never the message text. Each code links to its own page througherror.doc_url. - Read
error.retry:nomeans retrying as is cannot help. - Keep
error.request_id(also theX-Request-Idheader). Support needs it.
When a key or workspace problem is suspected, ask the API who you are. This request returns the key's workspace, mode and scopes, or the exact error with its fix link:
200 shows the workspace, the key's id, mode and current scopes. An error shows its error.code, error.request_id and the link to its page in error.doc_url.
Complete runnable example
First step for any 401 or 403: ask the API which workspace, mode and scopes your key has.
The same steps as a complete script, with error handling, tested against test mode on every docs build. Each script reads your test key from an environment variable named in its first lines.
- cURL script (bash)
check-key.sh - JavaScript (Node.js 20+)
check-key.mjs - Python 3.9+ (requests)
check-key.py
Requests fail with 401
The key is missing, malformed or no longer valid. The code tells you which:
authentication_required: NoAuthorizationheader, or not in the formBearer <key>. SendAuthorization: Bearer rai_test_...(one space, no quotes).invalid_api_key: The key does not exist: a typo, a truncated copy, extra whitespace or a newline from a file. Copy the key again; trim whitespace when reading it from a file or variable.api_key_expired: The key passed its expiry date. An admin creates a new key; plan rotations before expiry.api_key_revoked: An admin revoked the key. Use the replacement key.legacy_api_key_not_supported: An oldersk_live_...key. Create a scopedrai_live_...orrai_test_...key in the console.
Check the environment your program really runs with: a stale variable in a shell, container or CI secret is the most common cause.
Requests fail with 403
The key is valid but not allowed to do this. Five different causes:
insufficient_scope: The key was created without a scope this operation needs;details.required_scopeslists them. An admin creates a key that includes those scopes.insufficient_permissions: The scope is on the key, but the member who created it lost that permission (role change, left the workspace). An admin creates a new key from an account that has the permission.api_access_not_enabled: The workspace is not enabled for the API (private preview). Write to support@relationsai.com.ip_not_allowed: The workspace IP allowlist does not include your server. An admin adds your egress address in the console security settings.live_key_not_allowed_in_browser: A live key sent with anOriginheader, which browsers add. Call the API from your server; use a test key for browser experiments.
Also 403, but about the account or a feature rather than the key: account_inactive (trial ended or account suspended; reads still work) and webhooks_not_enabled (webhook delivery is not available yet; check capabilities.webhooks).
An id returns 404
resource_not_found means "no such object for this key". The API answers the same way for an id that does not exist, an id from the other mode, an id from another workspace and a malformed id, so as not to reveal anything. Check, in order:
- The mode. Test and live data are separate: a call, contact, campaign or webhook endpoint created with a test key does not exist for a live key, and the reverse. Only agents and phone numbers are shared (and the Sample agent and sample number exist only in test mode).
- The workspace. Is this key from the same workspace that created the object?
- The id itself. Copy it whole, with its prefix (
call_,cmp_,ct_...). Ids are opaque: do not build or shorten them.
route_not_found means the path does not exist: usually a base URL without /v1, a typo in the path, or a path from another API. method_not_allowed (405) means the path exists with another method.
Requests fail with 400 or 422 validation errors
validation_error(400): A field is missing, unknown, of the wrong type or out of range. Bodies are strict: unknown fields are refused.details.errorslists each problem withfield,issueandmessage. Fix each listed field.invalid_phone_number(400): Not E.164.+, country code, number, no spaces or punctuation:+919000000001.invalid_json(400): The body is not valid JSON. Serialise with your JSON library, do not build strings by hand.unsupported_media_type(415): Missing or wrongContent-Type. SendContent-Type: application/json.invalid_idempotency_key(400): You sent anIdempotency-Key(optional) with spaces, non-ASCII characters or is too long. 1 to 255 printable ASCII characters; a UUID is ideal.invalid_cursor(400): A cursor was changed, or reused with different filters. Sendnext_cursorunchanged, with the same filters.payload_too_large(413): The body is larger than 1 MB. Split the request. (Too many items in a list, for example more than 1000 recipients, is a400 validation_errorinstead.).custom_field_not_found(422): A custom field name is not defined in the console. Define it, or fix the name.
Conflicts: 409, 412 and 428
Idempotency conflicts happen only when you send the optional Idempotency-Key and it was already used:
idempotency_key_reused: the same key with a different request. You probably reused a key for a new action, or the body changed between retries (a timestamp in the body, a re-ordered list). Use a new key for a new action; keep retry bodies identical.idempotency_key_in_progress: the first attempt is still running. WaitRetry-After, then resend the same request with the same key.idempotency_outcome_unknown: the first attempt was interrupted and may or may not have taken effect. Check the state before acting (Retry and reliability practices).
State conflicts mean the object is not in a state that allows the action: campaign_state_conflict (read the campaign's capabilities), campaign_operation_in_progress (another start or resume is running), contact_already_exists (use upsert), contact_on_dnc_list, webhook_endpoint_disabled, webhook_delivery_not_retryable.
Concurrency: precondition_required (428) means a PATCH without If-Match; version_conflict (412) means the object changed since you read it. Read it again, reapply your change, and send the new ETag (Concurrency).
The agent is not ready
agent_not_ready lists every reason in details.reasons. Call GET/agents/{agent_id}/readiness for the same list with explanations, and fix them in the console: switch the agent on, publish it, assign an outbound number (Creating and preparing agents).
Related refusals about calling setup: phone_number_required (the agent has several outbound numbers: send phone_number_id), phone_number_not_usable (that number cannot call out for this agent), calling_not_configured (details.reason names the missing setup step), telephony_disconnected (reconnect the telephony account in the console).
Calls are refused
A refused call was not placed and not charged. The code says which rule applied:
dnc_listed: The number is on the do-not-call list. Do not call it.consent_required: Consent is required and the contact has none. Record consent only if it was given.outside_calling_hours: Outside the workspace calling window (details.window,details.timezone) Retry inside the window; the timezone is the workspace's, not the contact's.contact_daily_limit_reached: Too many calls to this number in 24 hours. Try later.call_not_accepted: The telephony network definitively refused the call. Check the number; contact support if valid numbers keep being refused.
See Consent and DNC. Never route around a refusal.
Balance and limits
insufficient_balance(402): The balance cannot cover the call or campaign;detailsshows both amounts. An admin tops up in the console.spend_limit_reached(402): The monthly spend limit set in the console is reached. An admin raises it.campaign_budget_reached(402): The campaign reached itsbudget_cap(spend can pass the cap by up to about 15 minutes of calls before the pause). Raise or clear the cap with PATCH, then start or resume.daily_call_limit_reached(429): The workspace's daily limit of calls placed through the API (live mode, resets 00:00 UTC) Wait for the reset; check for loops; ask support if the volume is expected.calling_capacity_reached(429): Too many call starts right now; nothing was dialled. WaitRetry-Afterand send it again; use a campaign for volume.contact_limit_reached(409): The contact store is at its current capacity. Delete contacts you no longer need, or contact support.
A campaign did not resume
campaign_recipients_blocked(409): Remaining recipients are now blocked (do-not-call, consent, daily limit);detailslists them. Do not stop and start it (a new start calls every previous recipient again, including people already reached). List the recipients withstatus=pending, create a new campaign with them minus the blocked ones, and stop this one.campaign_not_resumable(409): The paused run can no longer continue. To reach only the people not called yet, create a new campaign with the pending recipients (a new start of this campaign calls everyone again).campaign_recipients_unavailable(503): Recipients could not be screened right now; nothing was dialled. WaitRetry-Afterand send it again; if it keeps failing on a very large campaign, create a new campaign with the pending recipients.campaign_outcome_unknown(409): A start or resume was sent and not confirmed; dialling may have begun. Wait, then read the campaign; send the action again only if it did not start (with a new key if you use idempotency keys).campaign_dnc_override_enabled(409): Do-not-call screening is switched off for this campaign in the console, so the API refuses it. An admin switches it back on, or starts it from the console.campaign_state_conflict(409): The campaign is notpaused. Read itsstatusandcapabilities.
If a campaign paused by itself, the campaign.paused event's data.reason says why (budget, balance, spend limit, telephony, failure streak). Fix that first. A pause that answered campaign_pause_not_confirmed may still be dialling: read the campaign, then pause again or stop the campaign if it is still running.
An artifact is not ready
The transcript, recording or analysis answers 200 with state: "pending": the call has not settled. Wait for call.settled. state: "absent" means there is none (the call did not connect, or nobody spoke). A recording link that answers media_link_expired (410) passed its expires_at (about 15 minutes): read the recording again for a fresh link and download it straight away. A link that answers 404 before its expires_at belongs to a key that was revoked, expired, lost recordings:read or whose creator left the workspace: get a new link with a valid key. See Retrieving recordings and transcripts.
Webhooks do not arrive or fail verification
Work through these in order:
- Is delivery enabled?
capabilities.webhookson GET/workspacemust betrue. - Right mode, events and status? Test-mode events go only to endpoints created with a test key, live to live. Check the endpoint's
livemode,eventsandstatus: enabled. Events that occur while an endpoint is disabled are not delivered later. - What does the delivery log say? GET
/webhook-endpoints/{webhook_endpoint_id}/deliveries:erroristimeout(answer within 10 seconds),connection_failed,tls_error(check your certificate chain),blocked_destination(the URL resolves to a non-public address) orhttp_errorwith your server'sresponse_status. - Send a test event (POST
/webhook-endpoints/{webhook_endpoint_id}/test) and watch your server's logs. - Signature mismatch (your server answers
400): verify the raw body before parsing, use the wholewhsec_...secret as the key, accept any of severalv1values, and check your clock is within 5 minutes. Checksecret_last4against the secret you deployed. The verifier's command-line mode checks a saved delivery (Verifying webhook signatures). - Late or bursty deliveries: after 5 failed attempts in a row an endpoint is paused (1 minute, doubling up to 1 hour), at most 2 attempts run at once per endpoint, and test-mode deliveries are limited to 60 a minute per workspace. The backlog drains on its own once your endpoint answers
2xxagain; paused time does not use up attempts. - Lost signing secret: a create or rotate retried with the same optional idempotency key returns
secret: null. Rotate again and deploy that secret (Verifying webhook signatures). - Redirects are not followed: register the final HTTPS URL. Frameworks that require authentication or CSRF tokens on every POST must exempt the webhook path.
After fixing, redeliver failed deliveries with POST/webhook-deliveries/{webhook_delivery_id}/retry, and run a reconciliation for anything older.
Requests fail with 429
rate_limited means a rate limit for the key or the workspace was exceeded in the operation's class (read, write or dial). Wait the seconds in Retry-After, then slow down: read RateLimit-Remaining and RateLimit-Reset on every response, pace batch jobs, give each component its own key, and use webhooks and list requests instead of polling. Other keys of the same workspace share the workspace limit. The 429 codes daily_call_limit_reached and calling_capacity_reached are not rate limits; see Balance and limits.
Requests that are refused before they reach an operation (an invalid or missing key, an unknown route, a wrong method, a malformed body, an IP that is not allowed) are also limited per IP address, about 300 per minute. A script stuck on 401s can therefore get 429 rate_limited for every request from that machine, even with a valid key. Fix the underlying error first, and stop retrying 4xx answers in a loop.
Requests fail with 5xx
service_unavailable(503): a dependency is unavailable and the request was refused before it ran. The API fails closed: it never skips a check because something is down. Send it again afterRetry-After.internal_error(500): an unexpected fault. For a write, it may have been applied, and a retry (with or without an idempotency key) runs the request again. Read the resource first (for a call, list calls bytoandcreated_afterand match yourmetadata); retry with the same key only if it did not happen.- If either persists for more than a few minutes, write to support@relationsai.com with the request ids.
Getting help
Email support@relationsai.com with:
- the
request_idof one or more failing requests (from the error body or theX-Request-Idheader), - the time in UTC and the operation (method and path),
- the mode (test or live) and the key id (
key_..., from GET/workspace; never the key itself), - what you expected and what happened.
Never send API keys or full Authorization headers, webhook signing secrets, recording links, customer personal data (names, phone numbers, contact details) or transcripts, recordings and analysis of real calls; send ids (key_..., call_...) instead. See Support.