Skip to content
RelationsAI
API fundamentals

Requests and responses

Every request and response follows the same conventions. This page specifies content types, naming, the shape of single objects and lists, request ids and the headers returned on every response.

JSON only

  • Request and response bodies are JSON, UTF-8. Send Content-Type: application/json with every body; any other type answers unsupported_media_type, and a body that does not parse answers invalid_json.
  • An operation that documents no request body (for example pausing a campaign) ignores a body sent with it: it is not read, so it is never refused as 415, 413 or invalid_json.
  • HEAD is accepted wherever GET is and answers the same status and headers with no body.
  • Field names, query parameters and enum values are snake_case.
  • Request bodies are strict. A field the operation does not accept is validation_error, never silently ignored, so a typo cannot go unnoticed. Unknown query parameters are refused the same way, and each query parameter may be given once.
  • Request bodies are limited to 1 MB (payload_too_large). Some fields have their own limits, documented in the API reference.
  • GET requests have no body. Build JSON with a JSON library, not by concatenating strings.

validation_error lists every problem it found, not just the first:

JSONJSON
{ "error": { "code": "validation_error", "message": "One or more fields are invalid.", "details": { "errors": [{ "field": "agent_id", "issue": "required", "message": "agent_id is required." }] }, "request_id": "req_4k7m2qat5xvz3c6vqnwpbdfh", "retry": "no", "doc_url": "https://docs.relationsai.com/errors/validation_error" } }

Request headers

HeaderWhen
Authorization: Bearer <key>Every request. See Authentication.
Content-Type: application/jsonEvery request with a body.
Idempotency-KeyOptional on POST and DELETE: makes a retry safe. See Idempotency.
If-MatchRequired on PATCH of campaigns and contacts. See Concurrency.
RangeOptional, when downloading recording audio.

Objects

A single object is returned as itself, never wrapped. Every object has:

  • id: an opaque, prefixed id such as call_6Vb3Nm8Qw2Er5Ty9Ui4Op7;
  • object: its type, such as call, contact or campaign;
  • livemode: true for live mode, false for test mode.

Fields are always present in responses: a value that is not known or not set is null, not missing. A delete returns a small confirmation object:

JSONJSON
{ "id": "ct_8Rw2Tq5Lm9Xc3Vb7Nz4Kp1", "object": "contact", "livemode": false, "deleted": true }

Lists

Every list operation returns the same envelope:

JSONJSON
{ "object": "list", "data": [{ "id": "call_9Tg5Yh2Uj8Ik4Ol7Pm3Nb6", "object": "call", "livemode": false }], "has_more": false, "next_cursor": null }

data holds full objects (a call in a list is the same as from GET/calls/{call_id}). See Pagination and Filtering.

Status codes

StatusMeaning
200 OKSuccess.
201 CreatedA new object was created (for example a contact or a campaign).
202 AcceptedAccepted; the work continues after the response (placing a call, sending a test event, retrying a delivery). Follow the object's status.
206 Partial ContentA byte range of recording audio.
400, 413, 415, 422The request cannot be done as sent; fix it.
401, 403Authentication or permission problem.
402Balance or spend limit.
404No such object for this key, or no such route.
405Method not supported on this path.
409Conflict with the current state, or an idempotency conflict.
410A recording link has expired.
412, 428If-Match stale or missing.
429Rate limit, daily call limit or calling capacity.
500, 503A problem on our side.

Every error has the same envelope and a stable code; the status of each code never changes. See Error handling.

Request ids

Every response, success or error, has an X-Request-Id header:

Text
X-Request-Id: req_4k7m2qat5xvz3c6vqnwpbdfh

Error bodies repeat it as error.request_id (a replayed answer, marked Idempotent-Replayed: true, can carry the original request's id in the body; the header is always this request's). The id is generated by RelationsAI for each request; a value you send in an X-Request-Id request header is ignored. Log it with your own correlation id for every request, and quote it when you write to support: it is how we find your request.

Response headers

HeaderOnMeaning
X-Request-IdEvery responseThe request id.
RateLimit-LimitEvery responseRequests allowed in the current one-minute window.
RateLimit-RemainingEvery responseRequests left in the window.
RateLimit-ResetEvery responseSeconds until the window resets.
Retry-After429, 503 and errors whose retry is afterSeconds to wait before retrying.
Idempotent-ReplayedReplayed POST and DELETE responsestrue when the response is the stored result of an earlier request with the same key.
ETagCampaigns and contactsThe version to send as If-Match.
Allow405The methods the path supports.

See Rate limits. Browser code (from origins the API allows) can read X-Request-Id, the RateLimit-* headers, Retry-After, Idempotent-Replayed and ETag; the other headers are for server-side clients.

Asynchronous operations

Calls and campaigns run after the request returns. POST /v1/calls answers 202 with the call queued. Starting a campaign answers 200 when the run has started, or 202 when the start was sent and is still being confirmed. Follow progress with webhooks or by reading the object, and treat the object's own status as the truth.