Calls

Place outbound calls and read their results.

A call is a single phone interaction placed or answered by one of your agents. Place calls with POST /v1/calls, then read status, transcript, and extracted data back.

The call object (list shape):

{
  "id": "call_…",
  "contactId": "con_…",
  "campaignId": null,
  "channel": "voice",
  "direction": "outbound",
  "status": "completed",
  "outcome": "call_initiated",
  "startedAt": "2026-08-20T12:00:00Z",
  "endedAt": "2026-08-20T12:01:36Z",
  "durationSeconds": 96
}

durationSeconds is null until the call has both a start and end time. Internal fields (agent, room, account) are never returned.


Place a call

POST /v1/calls  ·  scope calls:write  ·  rate limited

curl -X POST https://api.oyehello.ai/v1/calls \
  -H "Authorization: Bearer ohk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "agt_…",
    "toNumber": "+919812345678",
    "externalRef": "your-record-id",
    "variables": { "name": "Nalin", "amount": "4999" }
  }'

Body

Field Type Required Notes
agentId string yes The agent that runs the call.
toNumber string yes Destination - a valid Indian +91 subscriber number (8–20 chars).
fromNumber string no Caller ID. Must be an active number you own. Defaults to your first active number.
externalRef string no Your own stable id (1–120 chars). Reuses the same contact across calls and comes back on webhooks. Omit and a synthetic contact keyed to the number is used.
variables object no String values substituted into the agent’s {variables} at call time.

Response - 200

This endpoint returns a bare object (no data wrapper):

{ "placed": true, "interactionId": "call_…", "providerCallId": "prov_…" }

Use interactionId to fetch the call later. The call runs the agent’s full compliance pipeline exactly as a campaign dial does.

Gates & errors

The same production gates that guard campaign dialing apply here:

Status Error Cause
404 agent not found Unknown agentId.
403 workspace suspended The workspace is suspended.
403 workspace must be verified (KYB) before placing calls Complete KYB first.
400 toNumber must be a valid Indian (+91) subscriber number Bad destination.
400 buy a phone number before placing calls No owned number to dial from.
402 insufficient wallet balance - top up to place a call Wallet can’t cover the call (in-flight calls counted).
422 call blocked by compliance: <reason> DND, opt-out, quiet-hours, or frequency cap.
429 concurrent-call limit reached … / platform is at capacity … Too many calls in flight - retry shortly.
503 India-resident voice providers are not configured … Workspace requires India-resident providers that aren’t set up.
502 call could not be placed Provider/dispatch failure.

List calls

GET /v1/calls  ·  scope calls:read

Query

Param Type Notes
limit int 1–200, default 50.
status string Optional - filter by call status.

Response - 200

{ "data": [ { "id": "call_…", "status": "completed",  } ] }

Returns a single page (there’s no cursor paging); raise limit up to 200 for more per request.


Get a call

GET /v1/calls/:id  ·  scope calls:read

Returns the full call, including the transcript, extracted fields, and a recording URL:

{
  "data": {
    "id": "call_…",
    "status": "completed",
    "outcome": "interested",
    "durationSeconds": 96,
    "transcript": [
      { "role": "agent", "text": "Hi, this is Riya from OyeHello…" },
      { "role": "customer", "text": "Yes, go ahead." }
    ],
    "extracted": { "callback": "Tuesday" },
    "recordingUrl": "https://…signed…"
  }
}
  • transcript - the diarized conversation ([] if none).
  • extracted - the structured fields the agent captured ({} if none).
  • recordingUrl - a signed playback URL if a recording exists, else null.

Unknown id → 404 { "error": "call not found" }.

Next: Agents →