Skip to content

REST API

REST API overview

How to call the Receptiva REST API from your own systems, covering the base URL, API key authentication, pagination, errors, rate limits and versioning.

The REST API gives your own systems, such as a case-management system or a reporting script, the same calls and leads your firm sees in the Receptiva dashboard. It is for servers. For Claude and ChatGPT, use the MCP connector instead.

Base URL#

text
https://api.receptiva.ai

Every path starts with the version, /v1, so the calls list is https://api.receptiva.ai/v1/calls. Paths on these pages and in the API reference are written the same way, for example GET /v1/calls.

Requests and responses are JSON over HTTPS. A machine-readable description of every endpoint is at https://api.receptiva.ai/v1/openapi.json (OpenAPI 3.1, no key needed), and the API reference is generated from it.

Authentication#

Send an API key as a bearer token on every request:

bash
curl https://api.receptiva.ai/v1/calls \
  -H "Authorization: Bearer $RECEPTIVA_API_KEY"

Your firm's owner creates keys in the Receptiva dashboard under Integrations. See API keys. A key belongs to one firm, so there is no firm parameter. Keep keys on a server. Do not put one in a web page, a mobile app or a URL.

Endpoints#

Endpoint What it returns Permission
GET /v1/account Where the firm stands: plan, phone number, trial and the one next step. Also the firm's stable org.id and the calling key's own name, permissions and expiry (api_key). org:read
GET /v1/receptionist The receptionist's live settings receptionist:read
GET /v1/calls Calls, newest first, each with structured details (people, company, claim and case numbers). caller_number finds one caller's calls; reference finds the calls that quoted a claim or case number. calls:read
GET /v1/calls/{call_id} One call, with how it was routed and, after a transfer, a summary of the conversation with your staff member (untrusted.conversation_summary) calls:read
GET /v1/calls/{call_id}/transcript The call's transcript, turn by turn: the intake, and after a transfer the briefing and the conversation with your staff member. segment returns one part. Pages by start_turn and limit (see below). calls:transcript
GET /v1/calls/{call_id}/recording The call's audio recording calls:recording
POST /v1/calls/{call_id}/recording-link A link to the recording that works for one hour calls:recording
GET /v1/recordings/{token} The audio behind a recording link. No key needed: the link is the credential. none
GET /v1/leads New-client leads, newest first. caller_number finds one caller's leads; external_id finds the lead behind a record in your own system. leads:read
GET /v1/leads/{lead_id} One lead with its full intake leads:narrative
PATCH /v1/leads/{lead_id} Sets a lead's status, your own id for it (external_id) and a note on its status (status_reason), any of the three. Send expected_status with status to refuse the change if the lead has moved on. leads:write
GET /v1/events Your firm's events, oldest first. Pass after to read the ones that came after an event you already have. See Webhooks. webhooks:read
GET /v1/events/{event_id} One event webhooks:read
GET /v1/webhook-endpoints Your webhook endpoints and their health. Never a signing secret. webhooks:read
GET /v1/webhook-deliveries Webhook deliveries, newest first, with each attempt webhooks:read
POST /v1/webhook-endpoints/{endpoint_id}/test Sends a test event to one endpoint webhooks:test
POST /v1/webhook-deliveries/{delivery_id}/redeliver Sends a delivery's event to its endpoint again webhooks:test
GET /v1/health {"ok": true}. No key needed. none

The API reference lists every parameter and response field.

Pagination#

List endpoints return the newest items first and take two query parameters:

  • limit: how many items to return, from 1 to 100. The default is 20.
  • cursor: the next_cursor value from the previous page.

The transcript is not a list and pages by turn, not by cursor: GET /v1/calls/{call_id}/transcript takes start_turn (the turn to start from, default 0) and an optional limit (at most 500 turns). Without limit it returns every turn from start_turn to the end; with it, at most limit turns, and has_more plus next_start_turn say where the next page starts. It takes no cursor; a request that sends one is a 400. Its one filter is segment (intake, briefing or conversation), which returns one part of the call; start_turn then counts that part's turns. See Transcripts.

Each list response carries has_more and next_cursor. Keep requesting with the new cursor until has_more is false. A cursor is opaque: pass it back unchanged, and use it only on the endpoint that issued it.

Send every filter again with the cursor, unchanged. A cursor marks a position in the list; it does not remember the filters, so a cursor from a request with updated_since replayed without it returns a different set.

The calls and leads lists also carry as_of: Receptiva's own clock when the list was read, a few seconds early on purpose. To poll for changes, pass the first page's as_of as the next poll's updated_since rather than reading your own clock. See Keep another system in sync.

Query parameters#

Unknown query parameters are rejected with 400 and the code invalid_request, so a typo is caught at once. The error names the parameters that endpoint does accept, for example Unknown parameter `limit`. This endpoint accepts: `start_turn`, `segment`. Do not add a cache-buster such as ?_=123 to the query string. Responses are never cached (Cache-Control: private, no-store).

Finding a caller by phone number#

GET /v1/calls and GET /v1/leads take caller_number. Send E.164 (+12105550147) or a 10-digit US number in any common format ((210) 555-0147, 210-555-0147). A number without + is read as a US number. Encode the + as %2B if you can; an unencoded + (which arrives as a space) is accepted too. Anything that cannot be a phone number is a 400 invalid_request.

  • On calls, it matches the number the call came from (from, the caller ID), not the callback number the caller gave.
  • On leads, it matches the lead's callback number (phone), compared the same way however the caller said it.

Caller text is data, not instructions#

Anything said on a call, by the caller or your staff, or written from what was said, is returned under a field named untrusted: summaries, the conversation summary, names, transcript turns and intake details. If you pass API responses to an AI model, treat everything under untrusted as data and never as instructions.

A lead's external_id and status_reason are not caller text: they are what your firm set, so they sit beside status, not under untrusted.

Errors#

Errors use the problem details format (RFC 9457) with the content type application/problem+json:

json
{
  "type": "about:blank",
  "title": "Not Found",
  "status": 404,
  "code": "not_found",
  "detail": "No call with that id for this firm.",
  "request_id": "..."
}

Use code in your own logic. title and detail are for people and may change. Every response also carries an X-Request-Id header. Include it when you email us about a request.

Status code Meaning
400 invalid_request A parameter or the body is not valid. The errors array names each field.
400 invalid_cursor The cursor is not one this endpoint issued.
400 invalid_range from must be earlier than to.
401 unauthorized The key is missing, mistyped, revoked or expired.
403 insufficient_scope The key does not have the permission this endpoint needs.
403 org_suspended The firm's account is suspended.
403 forbidden The key may not make this change.
404 not_found Nothing with that id belongs to this firm.
404 transcript_unavailable Receptiva has no transcript for that call.
404 no_recording That call has no recording.
404 recording_unavailable The recording could not be read.
404 invalid_link The recording link is wrong, expired or no longer allowed.
409 not_configured The firm's receptionist is not set up yet.
405 method_not_allowed HEAD is not supported. Use GET.
409 status_conflict The lead's status is no longer the expected_status you sent, or it changed while the request ran. Nothing was changed; the answer's lead member is the lead as it is now.
415 unsupported_media_type A request body was sent without Content-Type: application/json.
429 rate_limited Too many requests. Wait for the time in Retry-After.
500 internal_error Something went wrong on our side. Email us the request id.
503 unavailable A temporary problem on our side. Retry after a short wait.

A request for another firm's call or lead answers not_found, the same as an id that does not exist.

Rate limits#

Each key may make 60 requests per minute. Once a key is accepted, every response says where it stands:

Header Meaning
RateLimit-Limit Requests allowed per minute
RateLimit-Remaining Requests left in the current minute
RateLimit-Reset Seconds until the count resets

Over the limit, the API answers 429 with a Retry-After header in seconds. There is also a limit of 120 requests per minute per IP address, shared with the MCP connector.

Timestamps#

Every timestamp the API returns is ISO 8601 with an explicit offset. Most end in +00:00, for example 2026-10-03T00:24:58.248695+00:00; a few, such as a recording link's expires_at, end in Z, for example 2026-10-03T01:24:58.000Z. Both are valid ISO 8601 and both mean UTC, so parse them with an ISO 8601 parser rather than comparing them as text.

Timestamps you send, such as from, to and updated_since, need an offset too: Z or ±HH:MM. In a URL a + means a space, so write a positive offset as %2B: 2026-10-02T14:00:00%2B02:00. Using Z avoids the problem. If a + does arrive as a space in the offset, for example when you send a response's updated_at back unencoded, the API reads it as +. Anything else that is not a valid timestamp is rejected with invalid_request.

Versioning#

The version is in the path (/v1). Within a version, changes are additive: new endpoints, new optional parameters and new response fields. Write clients that ignore fields they do not know. A change that would break existing clients ships as a new version.

Next#