Skip to content
RelationsAI
Try it uses test keys only. Requests go straight from your browser to https://voice-api.relationsai.com/v1 with a rai_test_ key, which never places a real call. It works once the API is enabled for your workspace (private preview: support@relationsai.com). Your key stays in this tab's memory; live keys are refused.
RelationsAI API

RelationsAI API

Place AI voice calls, run calling campaigns and manage contacts from your own systems.

The RelationsAI API lets your servers place outbound AI voice calls, run campaigns, keep contacts and the do-not-call list in sync, and receive events by webhook. It is a JSON-over-HTTPS REST API.

Private preview. The API is enabled per workspace. A workspace that has not been enabled gets 403 api_access_not_enabled on every request. Write to support@relationsai.com to request access.

Authentication

Every request sends an API key as a bearer token: Authorization: Bearer <key>. Keys are created in the console (API & Developers) by a workspace admin and are shown once.

  • rai_test_... keys work in test mode: nothing is dialled, nothing is charged, and every object has "livemode": false.
  • rai_live_... keys work in live mode: real calls to real people, charged to the workspace balance.
  • Older sk_live_... keys are not accepted by this API (401 legacy_api_key_not_supported).

Each key carries scopes (for example calls:write). Each operation lists the scopes it needs under x-required-scopes. A key can never do more than the member who created it is currently allowed to do. Live keys are refused from browsers (requests with an Origin header).

Test mode and live mode

Test mode and live mode are fully separate. Calls, campaigns, recipients, contacts, do-not-call entries, webhook endpoints, events and deliveries created with a test key exist only in test mode, and the same for live. An id from the other mode is answered with 404 resource_not_found. Agents and phone numbers are your workspace's real configuration in both modes; test mode also has a built-in Sample agent and a sample number +12025550150, so you can try everything before your own agent is ready.

In test mode the destination number chooses what happens to a call:

DestinationSimulated result
+12025550100Answered and completed, with transcript, recording and analysis
+12025550101No answer
+12025550102Busy
+12025550103Failed (network error)
+12025550104Answered by voicemail: no_response
+12025550105Completed after a longer conversation
+12025550106The person hangs up early: completed, short duration
any other numberCompleted

A test call reaches ended within about 20 seconds and settled within about 30 seconds. Do-not-call entries and missing consent refuse calls in test mode exactly as in live mode.

Requests and responses

  • Bodies are JSON with Content-Type: application/json. Field names are snake_case. Request bodies are strict: an unknown field is a 400 validation_error, never silently ignored.
  • Ids are opaque strings with a type prefix (call_, cmp_, ct_, ...). Treat them as opaque.
  • Timestamps are RFC 3339 in UTC, for example 2026-10-21T09:30:00.000Z.
  • Money is {"amount": <integer minor units>, "currency": "INR"}, always what your workspace is charged.
  • Phone numbers are E.164 strings, for example +919000000001.
  • Enums are open. New values can be added at any time; treat an unknown value as "other", never as success.
  • Every response carries X-Request-Id. Quote it to support.
  • HEAD is accepted wherever GET is and answers the same status and headers with no body.
  • An operation that documents no request body ignores a body sent with it (it is not read, so it is never refused as 415, 413 or invalid_json).
  • Responses carry no automatic ETag; only campaigns and contacts have one (see Concurrency), and conditional GET headers (If-None-Match, If-Modified-Since) are ignored: a response is never 304.

Pagination

List operations return {"object": "list", "data": [...], "has_more": bool, "next_cursor": string|null}, newest first. Pass limit (1 to 100, default 25) and, for the next page, cursor set to the previous next_cursor with the same filters. Cursors are opaque and bound to the filters that produced them.

Idempotency

The Idempotency-Key header is optional on every POST and DELETE: an extra safety for retries. Without it every request runs, so a retry after a timeout or a lost response can repeat the action (place a second call, start a campaign again); before retrying, check the state with a GET (for a call, list calls by to and created_after). With it (1 to 255 printable ASCII characters, no spaces; a UUID v4 is ideal), a retry is safe. Keys are scoped to your workspace and mode and kept for 24 hours. What is stored under a key: a successful (2xx) answer, and the few errors whose documentation says the outcome is unknown and the answer is kept (for example campaign_outcome_unknown). A retry with the same key and the same request gets that stored answer again with Idempotent-Replayed: true (the same data and status; request_id and X-Request-Id are the retry's own, and a stored retry: after error carries its Retry-After). Any other error is not stored: a retry with the same key runs the request again (each error page says what to check first). Reusing a key for a different request is 409 idempotency_key_reused. If the first attempt is still running you get 409 idempotency_key_in_progress with Retry-After. If you send keys, never generate a new key to retry a request that may have succeeded: that is how a request runs twice.

Concurrency

Campaigns and contacts carry an ETag response header. PATCH on them requires If-Match with that value (428 precondition_required if missing, 412 version_conflict if the resource changed). If-Match: * matches any current version and applies the update without the concurrency check.

Errors

Every error has the same envelope: {"error": {"code", "message", "details", "request_id", "retry", "doc_url"}}. Branch on code. retry tells you what to do: no (fix the request or the configuration), yes (retry with backoff: with the same Idempotency-Key if you sent one, otherwise after checking the state with a GET) or after (wait Retry-After seconds). Some errors can happen after work may have begun; their documentation says so and tells you to retry with the same key and then check the resource state.

Rate limits

Limits apply per key and per workspace, separately in test and live mode, in three classes: read (600 per minute per key, 1200 per workspace), write (120 per key, 240 per workspace) and dial (placing calls and starting or resuming campaigns: 30 per minute per workspace). Requests that end before they reach an operation (an unknown path, a refused body, a missing or invalid key, a workspace without API access) are counted per client IP address: 300 per minute, after which every request from that address answers 429 rate_limited until the window resets. Live mode also caps calls placed through the API per UTC day (429 daily_call_limit_reached): each POST /v1/calls counts one, a campaign start counts every recipient and is refused whole if they do not all fit, and a resume counts nothing. Every response has RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (for the tightest limit that applied); 429 and 503 responses have Retry-After. Each operation's class is shown as x-rate-limit-class.

Asynchronous work

Calls and campaigns run after the request returns. POST /v1/calls answers 202 with a call in queued status; follow it with webhooks (call.ended, call.settled) or by polling GET /v1/calls/{call_id}. Transcripts, recordings and analysis become ready after the call ends, usually within a minute and sometimes several minutes later; call.settled says everything, including the charge, is final.

Versioning

The major version is in the path (/v1). Additive changes (new operations, fields, enum values, event types, error codes) ship without advance notice and are recorded in the changelog. Breaking changes need a new major version and at least six months' notice. Events carry api_version.

Tags
Security Schemes
Test API key
An API key created in the console under API & Developers, sent as Authorization: Bearer <key>. rai_test_ keys use test mode and rai_live_ keys use live mode.
http
Webhooks
call.ended

Call ended

POST
call.settled

Call settled

POST
campaign.started

Campaign started

POST
campaign.paused

Campaign paused

POST
campaign.resumed

Campaign resumed

POST
campaign.stopped

Campaign stopped

POST
campaign.completed

Campaign completed

POST
campaign.failed

Campaign failed

POST
webhook_endpoint.test

Test event

POST