Making calls

Place an outbound AI call, follow it to a terminal status, read the transcript and summary, and hang up early when you need to.

Give Dairo a destination, a caller id you own, and instructions, and an AI agent makes the call. This page walks the whole lifecycle: place, poll, read, hang up.

OperationMethod & pathScope
Place a callPOST /v1/phone/callsphone:call
List callsGET /v1/phone/callsphone:read
Get one callGET /v1/phone/calls/{id}phone:read
TranscriptGET /v1/phone/calls/{id}/transcriptphone:read
RecordingGET /v1/phone/calls/{id}/recordingphone:read
Hang upPOST /v1/phone/calls/{id}/hangupphone:call

Place a call

Pass to, from, and instructions — everything else is optional. from must be a number your account owns with status active; buy one on Phone numbers.

curl -X POST https://api.dairo.app/v1/phone/calls \
  -H "Authorization: Bearer $DAIRO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: api-recovered-2026-07-09" \
  -d '{
    "to": "+13125550147",
    "from": "+13125550100",
    "instructions": "Tell {{customerName}} the API recovered at 14:02 and all alarms are green. Answer questions about the outage window if asked. Keep it under twenty seconds.",
    "greeting": "Hallo {{customerName}}, hier ist der Dairo-Agent von Acme.",
    "language": "de",
    "voice": "marin",
    "maxDurationSeconds": 300,
    "voicemailMessage": "Hallo Sam, kurze Info: die API läuft seit 14:02 wieder. Alle Alarme sind grün.",
    "variables": { "customerName": "Sam" },
    "metadata": { "incidentId": "inc_482" }
  }'

You get back a phone_call at status: "queued" — the request returns before the phone rings, and the call proceeds on its own:

{
  "object": "phone_call",
  "id": "7c9e6679-7425-40de-963d-04cf43a4b1c3",
  "direction": "outbound",
  "status": "queued",
  "fromNumber": "+13125550100",
  "toNumber": "+13125550147",
  "instructions": "Tell Sam the API recovered at 14:02…",
  "greeting": "Hallo Sam, hier ist der Dairo-Agent von Acme.",
  "voice": "marin",
  "model": "dairo-live",
  "language": "de",
  "maxDurationSeconds": 300,
  "voicemailMessage": "Hallo Sam, kurze Info: die API läuft seit 14:02 wieder. Alle Alarme sind grün.",
  "durationSeconds": null,
  "summary": null,
  "costUsd": null,
  "error": null,
  "metadata": { "incidentId": "inc_482" },
  "idempotencyKey": "api-recovered-2026-07-09",
  "createdAt": "2026-07-09T14:03:20Z"
}

The CLI's --wait flag polls the call for you until it reaches a terminal status, then prints the transcript — the fastest way to try your first call end to end.

Request fields

FieldRequiredDescription
toYesDestination, E.164 (e.g. +13125550147).
fromYesCaller id, E.164. Must be a number your account owns with status active — anything else is rejected with 400.
instructionsYesWhat the agent should accomplish, up to 8,000 characters. This is the whole conversation brief: goal, tone, facts it may share, length.
greetingThe opening line, spoken when the callee answers, right after Dairo's fixed AI and recording disclosure.
voiceOne of the 22 Dairo Live voice ids, e.g. marin (the default) or cedar. Any other value is rejected with 400. See Voices.
modeldairo-live, the one model every call runs on — you can leave it out. Any other value is rejected with 400.
languageISO code of the language the agent speaks, e.g. de. Calls default to English; the disclosure follows this language, or the callee's country when it is unset.
maxDurationSecondsHard cap on call length, 30–780 seconds (default 600). Dairo ends the call at the cap.
voicemailMessageUp to 500 characters. When a voicemail picks up, the agent leaves exactly this message and hangs up; unset, it hangs up silently.
variablesValues for {{name}} placeholders in instructions and greeting, filled in when the call is created.
metadataOpaque key/value object, stored and echoed back on the call.
idempotencyKeyAlso accepted as an Idempotency-Key header; when both are set they must match.

from is not decoration. The caller id must be a number your account owns with status active — that rule is what keeps a key with phone:call from impersonating someone else's number. Pick a from your recipient's carrier will display: for German recipients that means a German local or national number, and Compliance explains why.

Fill in per-call values with variables

instructions and greeting support {{name}} placeholders, filled from variables when the call is created — the call object shows the filled-in text, exactly what the agent was told. A placeholder with no matching variable stays as written. Keep the brief as a stable template and inject the per-call facts:

{
  "instructions": "Remind {{customerName}} that invoice {{invoiceId}} ({{amount}}) is due Friday. Offer to resend it by email.",
  "variables": { "customerName": "Frau Weber", "invoiceId": "INV-2201", "amount": "EUR 1,240" }
}

Retry safely

Pass an Idempotency-Key header (or idempotencyKey in the body). A retried request with the same key returns the original call and never dials twice — with phones, a duplicate isn't a wasted API call, it's a human being rung a second time. Use a key derived from the reason for the call, such as an incident id or an invoice id; Retries & idempotency has the general contract.

Poll the call

Fetch the call until it reaches a terminal status: completed, failed, no_answer, busy, or canceled.

curl https://api.dairo.app/v1/phone/calls/7c9e6679-7425-40de-963d-04cf43a4b1c3 \
  -H "Authorization: Bearer $DAIRO_API_KEY"

A finished call carries everything you need to reason about what happened — how long it ran, what it cost, and a one-paragraph summary of the conversation:

{
  "object": "phone_call",
  "id": "7c9e6679-7425-40de-963d-04cf43a4b1c3",
  "status": "completed",
  "fromNumber": "+13125550100",
  "toNumber": "+13125550147",
  "durationSeconds": 42,
  "summary": "Reached Sam, relayed that the API recovered at 14:02 with all alarms green. He acknowledged and had no questions.",
  "costUsd": 0.1362,
  "createdAt": "2026-07-09T14:03:20Z"
}

Dairo writes the summary from the transcript, in the language the conversation was held in. On a call that just ended, summary can be null for a moment — poll once more and it fills in.

List recent calls with GET /v1/phone/calls, filtered by status or to; limit is 1–100 and defaults to 50:

cURL
curl "https://api.dairo.app/v1/phone/calls?status=completed&limit=20" \
  -H "Authorization: Bearer $DAIRO_API_KEY"

Get notified instead of polling

Subscribe a webhook to call.status_changed and Dairo tells you when a call is answered (status: "in_progress") and again when it reaches its terminal status — no polling loop required:

{
  "type": "call.status_changed",
  "data": {
    "callId": "7c9e6679-7425-40de-963d-04cf43a4b1c3",
    "status": "completed",
    "to": "+13125550147",
    "from": "+13125550100",
    "durationSeconds": 42,
    "hangupCause": "normal_clearing",
    "costUsd": 0.1362
  }
}

durationSeconds, hangupCause, and costUsd are null until the call has ended. The agent ends the conversation by itself when the person says goodbye, so most calls finish well before maxDurationSeconds.

Read the transcript

Every conversation produces a turn-by-turn transcript. Roles are agent (your AI), caller (the human who answered), and tool. A call that hasn't produced any conversation yet returns empty turns.

curl https://api.dairo.app/v1/phone/calls/7c9e6679-7425-40de-963d-04cf43a4b1c3/transcript \
  -H "Authorization: Bearer $DAIRO_API_KEY"
{
  "object": "phone_call.transcript",
  "callId": "7c9e6679-7425-40de-963d-04cf43a4b1c3",
  "turns": [
    { "role": "agent", "content": "Hallo Sam, hier ist der Dairo-Agent von Acme.", "timestamp": "2026-07-09T14:03:41Z" },
    { "role": "caller", "content": "Ja, hallo?", "timestamp": "2026-07-09T14:03:44Z" },
    { "role": "agent", "content": "Kurze gute Nachricht: die API läuft seit 14:02 wieder, alle Alarme sind grün.", "timestamp": "2026-07-09T14:03:47Z" }
  ]
}

Fetch the recording

When a call has a recording, GET /v1/phone/calls/{id}/recording resolves it to a storage object you can download or share:

{
  "object": "phone_call.recording",
  "callId": "7c9e6679-7425-40de-963d-04cf43a4b1c3",
  "objectId": "obj_9f2c4a7d"
}

Every connected call is recorded. A call with no recording returns 404 — because it never connected, or the audio isn't available yet.

Hang up

End a live call early. Hanging up works at any point before a terminal status — a queued call is canceled before it ever dials, and a call that is already talking stops immediately.

curl -X POST https://api.dairo.app/v1/phone/calls/7c9e6679-7425-40de-963d-04cf43a4b1c3/hangup \
  -H "Authorization: Bearer $DAIRO_API_KEY"

The call settles as canceled. If it had connected, the time already spoken is still billed and transcribed. Hanging up a call that already reached a terminal status returns 409.

On this page