Skip to content
RelationsAI
Getting started

Quickstart: your first call

Test mode

In this quickstart you check an API key, pick an agent, place a call in test mode, follow it until it is final, and read its transcript, recording and analysis: eight requests, about five minutes.

Before you begin

  • A RelationsAI workspace with API access. The API is in private preview: a workspace that has not been enabled answers every authenticated request with api_access_not_enabled. Write to support@relationsai.com to request access.
  • A workspace owner or admin to create the key (only they can create keys).
  • One of: curl; Node.js 20 or newer (built-in fetch); Python 3.9 or newer with requests (pip install requests).
  • No agent of your own is needed. Test mode has a built-in Sample agent (agent_1ZltiWzxrXmMprGD6q63) that is always ready.
  1. Create a test API key

    In the RelationsAI platform (voice.relationsai.com), open Settings → API Keys and select Create API key (step by step: Get an API key):

    1. Name it after what will use it, for example Quickstart.
    2. Choose the Test environment.
    3. Keep the default read permissions and also tick Place calls (calls:write).
    4. Choose an expiry. Test keys may also be set to never expire.
    5. Select Create API key, copy the key and confirm that you have stored it. The key is shown once.

    In the requests on this page, replace <token> with that key. The ids shown (call_6Vb3Nm8Qw2Er5Ty9Ui4Op7 and so on) are examples: use the ids from your own responses. Keep the key in a secret manager, never in source code. See API keys for scopes, expiry and rotation.

  2. Check the key

    Retrieve the workspace. It is the quickest way to check that a key works, which mode it is in and which scopes it has, and it never changes anything.

    GET/workspace
    curl https://voice-api.relationsai.com/v1/workspace \ -H "Authorization: Bearer <token>"
    JSONJSON
    { "id": "ws_7Hk2Lm9QxR4tB8vN3cZp6W", "object": "workspace", "livemode": false, "name": "Acme Lending", "timezone": "Asia/Kolkata", "currency": "INR", "api_key": { "id": "key_4Nf8Gt2Hx6Jq9Lm3Pv7Rs1", "name": "CRM sync (test)", "environment": "test", "scopes": [ "workspace:read", "agents:read", "numbers:read", "calls:read", "calls:write", "transcripts:read", "recordings:read", "analysis:read" ], "created_at": "2026-10-20T08:15:00.000Z", "expires_at": null }, "capabilities": { "campaign_pause": true, "campaign_resume": true, "call_ringing_status": false, "live_transcript": false, "sentiment_analysis": false, "end_call": false, "agent_editing": false, "inbound_calls": false, "webhooks": true }, "limits": { "rate_limits": { "read": { "per_key_per_minute": 600, "per_workspace_per_minute": 1200 }, "write": { "per_key_per_minute": 120, "per_workspace_per_minute": 240 }, "dial": { "per_key_per_minute": null, "per_workspace_per_minute": 30 } }, "daily_call_limit": null, "campaign_max_concurrency": 10 } }

    Next: check that livemode is false (a test key) and that api_key.scopes contains calls:write.

  3. List agents

    List the agents your key can use. Test mode lists your workspace's real agents plus the built-in Sample agent, marked "sample": true.

    GET/agents
    curl https://voice-api.relationsai.com/v1/agents \ -H "Authorization: Bearer <token>"
    JSONJSON
    { "object": "list", "data": [ { "id": "agent_3hT9kQ2mVx7RbN4pLc8Wz1", "object": "agent", "livemode": false, "name": "Loan renewal reminder", "description": "Reminds customers that their loan is due for renewal and books a callback.", "status": "active", "published": true, "published_version": 4, "is_default": true, "sample": false, "created_at": "2026-09-02T10:12:44.000Z", "updated_at": "2026-10-18T14:03:10.000Z" }, { "id": "agent_1ZltiWzxrXmMprGD6q63", "object": "agent", "livemode": false, "name": "Sample agent", "description": "A built-in agent for trying the API in test mode.", "status": "active", "published": true, "published_version": 1, "is_default": false, "sample": true, "created_at": "2026-10-21T00:00:00.000Z", "updated_at": null } ], "has_more": false, "next_cursor": null }

    Next: use the Sample agent's id, agent_1ZltiWzxrXmMprGD6q63. It has the same id in every workspace, exists only in test mode, and calls from the sample number +12025550150. If has_more is true, see Pagination.

  4. Check that the agent is ready

    Ask whether the agent can place calls right now. The Sample agent is always ready; one of your own agents reports every problem it has, such as being switched off in the console.

    GET/agents/{agent_id}/readiness
    curl https://voice-api.relationsai.com/v1/agents/agent_1ZltiWzxrXmMprGD6q63/readiness \ -H "Authorization: Bearer <token>"
    JSONJSON
    { "object": "agent_readiness", "livemode": true, "agent_id": "agent_3hT9kQ2mVx7RbN4pLc8Wz1", "ready": true, "reasons": [], "checked_at": "2026-10-21T09:30:00.000Z" }

    This example from the API reference is a live agent; for the Sample agent in test mode, agent_id is agent_1ZltiWzxrXmMprGD6q63 and livemode is false.

    Next: continue when ready is true. Otherwise each entry in reasons says what to fix in the console.

  5. Place a test call

    Send the agent and a destination number. In test mode the destination chooses the simulated result: +12025550100 answers and completes a conversation. variables are values the agent can use in the conversation; metadata is your own data, returned as sent. Each POST /v1/calls places a call: if a request times out, list your calls before sending it again, or send an optional Idempotency-Key so that a retry returns the same call (see Idempotency).

    POST/calls
    curl -X POST https://voice-api.relationsai.com/v1/calls \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "agent_1ZltiWzxrXmMprGD6q63", "to": "+12025550100", "variables": { "first_name": "Asha", "amount_due": "4,200" }, "metadata": { "crm_lead_id": "L-20931" } }'
    JSONJSON
    { "id": "call_6Vb3Nm8Qw2Er5Ty9Ui4Op7", "object": "call", "livemode": false, "status": "queued", "outcome": null, "end_reason": null, "direction": "outbound", "source": "api", "agent_id": "agent_1ZltiWzxrXmMprGD6q63", "campaign_id": null, "phone_number_id": "pn_6Vhm3sFrB5UpmXAnsvmDu", "from": "+12025550150", "to": "+12025550100", "metadata": { "crm_lead_id": "L-20931" }, "created_at": "2026-10-21T09:30:00.000Z", "ended_at": null, "duration_seconds": null, "billable_minutes": null, "cost": null, "artifacts": { "transcript": { "state": "pending" }, "recording": { "state": "pending" }, "analysis": { "state": "pending" } }, "settled": false, "settled_at": null }

    202 means the call passed every check and was accepted; it does not mean anyone answered. Other test numbers simulate other results:

    DestinationSimulated resultoutcome
    +12025550100Answered and completed, with transcript, recording and analysiscompleted
    +12025550101No answerno_answer
    +12025550102Busybusy
    +12025550103Failed: the call could not be connectedfailed
    +12025550104Answered by voicemail; nobody spokeno_response
    +12025550105Completed after a longer conversationcompleted
    +12025550106The person hangs up early; short durationcompleted
    Any other valid numberCompletedcompleted

    Next: keep the call's id and read the call until it is settled.

  6. Retrieve the call

    Read the call. A test call reaches status: ended within about 20 seconds and settled: true within about 30 seconds; settled means everything about the call is final: outcome, duration, artifacts and charge.

    GET/calls/{call_id}
    curl https://voice-api.relationsai.com/v1/calls/call_6Vb3Nm8Qw2Er5Ty9Ui4Op7 \ -H "Authorization: Bearer <token>"
    JSONJSON
    { "id": "call_2Hd8Ks4Lq7Mw3Nx9Pz5Rc1", "object": "call", "livemode": true, "status": "ended", "outcome": "completed", "end_reason": "hangup", "direction": "outbound", "source": "api", "agent_id": "agent_3hT9kQ2mVx7RbN4pLc8Wz1", "campaign_id": null, "phone_number_id": "pn_5Lq8Xc2Rk9TzW3mBv7Np4d", "from": "+918000000001", "to": "+919000000001", "metadata": { "crm_lead_id": "L-20931" }, "created_at": "2026-10-21T09:30:00.000Z", "ended_at": "2026-10-21T09:32:14.000Z", "duration_seconds": 118, "billable_minutes": 2, "cost": { "amount": 600, "currency": "INR" }, "artifacts": { "transcript": { "state": "ready" }, "recording": { "state": "ready" }, "analysis": { "state": "ready" } }, "settled": true, "settled_at": "2026-10-21T09:41:02.000Z" }

    This example from the API reference is a settled live call; a settled test call looks the same except that livemode is false and cost.amount is always 0.

    Next: repeat this request every few seconds until settled is true. In production, let RelationsAI tell you instead (last step).

  7. Read the transcript

    Once artifacts.transcript.state is ready, read the conversation turn by turn, verbatim. This needs transcripts:read, which is in the default scopes of a new key.

    GET/calls/{call_id}/transcript
    curl https://voice-api.relationsai.com/v1/calls/call_6Vb3Nm8Qw2Er5Ty9Ui4Op7/transcript \ -H "Authorization: Bearer <token>"
    JSONJSON
    { "object": "call_transcript", "livemode": true, "call_id": "call_2Hd8Ks4Lq7Mw3Nx9Pz5Rc1", "state": "ready", "simulated": false, "turns": [ { "index": 0, "speaker": "agent", "text": "Hello, this is an AI assistant calling from Acme Lending. Am I speaking with Asha?", "start_ms": 400, "end_ms": 4900 }, { "index": 1, "speaker": "caller", "text": "Yes, speaking.", "start_ms": 5600, "end_ms": 6500 } ] }

    In test mode the transcript is a fixture with "simulated": true and "livemode": false; this example from the API reference comes from a live call.

    Next: read the recording.

  8. Ask for a short-lived link to the audio (about 15 minutes, see expires_at). This needs recordings:read.

    GET/calls/{call_id}/recording
    curl https://voice-api.relationsai.com/v1/calls/call_6Vb3Nm8Qw2Er5Ty9Ui4Op7/recording \ -H "Authorization: Bearer <token>"
    JSONJSON
    { "object": "call_recording", "livemode": true, "call_id": "call_2Hd8Ks4Lq7Mw3Nx9Pz5Rc1", "state": "ready", "simulated": false, "url": "https://voice-api.relationsai.com/v1/recordings/rec_tok_Y2FsbF8ySGQ4S3M0THE3TXczTng5UHo1UmMx", "expires_at": "2026-10-21T10:00:00.000Z", "content_type": "audio/ogg" }

    The link works without an API key, so treat it like a password: use it right away, do not log or store it, and ask for a new one when you need it. It also stops working as soon as the key that obtained it is revoked or expires, loses recordings:read, or its creator leaves the workspace. In test mode the audio is a short synthetic audio/wav file and the recording has "simulated": true.

    Next: download the audio with a plain GET of url (no Authorization header), or go on to the analysis.

  9. Read the analysis

    Read the summary, the fields the agent extracts (configured in the console) and whether the call met its goal. This needs analysis:read.

    GET/calls/{call_id}/analysis
    curl https://voice-api.relationsai.com/v1/calls/call_6Vb3Nm8Qw2Er5Ty9Ui4Op7/analysis \ -H "Authorization: Bearer <token>"
    JSONJSON
    { "object": "call_analysis", "livemode": true, "call_id": "call_2Hd8Ks4Lq7Mw3Nx9Pz5Rc1", "state": "ready", "simulated": false, "summary": { "state": "ready", "text": "The customer confirmed the renewal and asked for a callback on Friday afternoon." }, "extracted_fields": [ { "name": "wants_renewal", "type": "boolean", "state": "ready", "value": true }, { "name": "callback_day", "type": "string", "state": "ready", "value": "Friday" } ], "goal_met": true }

    In test mode the analysis is a fixture with "simulated": true. More in Retrieving recordings and transcripts and Working with analysis results.

    Next: stop polling and receive events instead.

  10. Get notified instead of polling

    Register a webhook endpoint and RelationsAI sends your server a signed call.ended event when the call ends and call.settled when everything is final, in test mode too. Webhook delivery is a separate private preview: check that capabilities.webhooks is true in the workspace response from step 2 before you build on it. Follow Receiving webhooks, then Verifying webhook signatures.

Run the whole flow

The same flow as one tested program in cURL, JavaScript and Python: it checks the key (and refuses anything but a test key), finds the Sample agent, checks readiness, places a call to +12025550100, polls until the call settles and prints the transcript, the recording state and the analysis.

Complete runnable example

Quickstart: place a simulated call in test mode and read its results.

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.

What can go wrong

  • authentication_required: No Authorization header, or not in the form Bearer <key>. Check that the Authorization header carries your key in place of <token>.
  • invalid_api_key: The key is mistyped, truncated or not an API key. Copy it again from where you stored it. If it is lost, create a new key: secrets are shown only once.
  • legacy_api_key_not_supported: An older sk_live_ key. Create a scoped key under Settings → API Keys.
  • api_access_not_enabled: The workspace is not in the API preview. Write to support@relationsai.com to request access.
  • insufficient_scope on POST /calls: The key has no calls:write. Create a test key with Place calls ticked. details.required_scopes lists what is missing.
  • live_key_not_allowed_in_browser: A live key was sent from browser code. Use a test key, and call the API from a server.
  • resource_not_found: The id is wrong, from another workspace or from the other mode. The Sample agent exists only in test mode; copy ids from your own responses.
  • agent_not_ready: One of your own agents cannot call yet. Read its readiness reasons and fix them in the console, or use the Sample agent.
  • dnc_listed or consent_required: Your test-mode do-not-call list or consent rules refuse the number. Test mode applies these exactly as live mode. Use another test number, or see Consent and do-not-call.
  • rate_limited: Too many requests in a minute. Wait for Retry-After seconds. See Rate limits.

Every error has the same shape and a request_id; see Error handling.

Check that it worked

  • The call's settled is true, outcome is completed and all three artifacts are ready.
  • Every object you received has "livemode": false, and the call's cost.amount is 0. Nothing was charged.
  • Test-mode calls are kept apart from live data: they do not appear in the console's call history or to live keys. Read them with your test key.

Next steps