> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dairo.app/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

| Operation    | Method & path                         | Scope        |
| ------------ | ------------------------------------- | ------------ |
| Place a call | `POST /v1/phone/calls`                | `phone:call` |
| List calls   | `GET /v1/phone/calls`                 | `phone:read` |
| Get one call | `GET /v1/phone/calls/{id}`            | `phone:read` |
| Transcript   | `GET /v1/phone/calls/{id}/transcript` | `phone:read` |
| Recording    | `GET /v1/phone/calls/{id}/recording`  | `phone:read` |
| Hang up      | `POST /v1/phone/calls/{id}/hangup`    | `phone: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](/phone/phone-numbers).

<CodeGroup>
  ```bash title="cURL" theme={null}
  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": "+4915112345678",
      "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",
      "maxDurationSeconds": 300,
      "variables": { "customerName": "Luka" },
      "metadata": { "incidentId": "inc_482" }
    }'
  ```

  ```ts title="TypeScript" theme={null}
  const call = await dairo.calls.create({
    to: "+4915112345678",
    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",
    maxDurationSeconds: 300,
    variables: { customerName: "Luka" },
    metadata: { incidentId: "inc_482" },
    idempotencyKey: "api-recovered-2026-07-09",
  });
  console.log(call.id, call.status); // … queued
  ```

  ```python title="Python" theme={null}
  call = dairo.calls.create(
      to="+4915112345678",
      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",
      max_duration_seconds=300,
      variables={"customerName": "Luka"},
      metadata={"incidentId": "inc_482"},
      idempotency_key="api-recovered-2026-07-09",
  )
  print(call.id, call.status)  # … queued
  ```

  ```bash title="CLI" theme={null}
  dairo phone call +4915112345678 \
    --from +13125550100 \
    --instructions "Tell {{customerName}} the API recovered at 14:02. Keep it under twenty seconds." \
    --greeting "Hallo {{customerName}}, hier ist der Dairo-Agent von Acme." \
    --language de \
    --max-duration 300 \
    --variables '{"customerName":"Luka"}' \
    --idempotency-key api-recovered-2026-07-09 \
    --wait
  ```

  ```text title="MCP" theme={null}
  Tool: make_phone_call  (scope phone:call, confirm required)
  Args: { "to": "+4915112345678", "from": "+13125550100", "instructions": "Tell Luka the API recovered at 14:02. Keep it under twenty seconds.", "confirm": true }
  ```
</CodeGroup>

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

```json theme={null}
{
  "object": "phone_call",
  "id": "7c9e6679-7425-40de-963d-04cf43a4b1c3",
  "direction": "outbound",
  "status": "queued",
  "fromNumber": "+13125550100",
  "toNumber": "+4915112345678",
  "instructions": "Tell {{customerName}} the API recovered at 14:02…",
  "greeting": "Hallo {{customerName}}, hier ist der Dairo-Agent von Acme.",
  "language": "de",
  "maxDurationSeconds": 300,
  "durationSeconds": null,
  "summary": null,
  "costUsd": null,
  "error": null,
  "metadata": { "incidentId": "inc_482" },
  "idempotencyKey": "api-recovered-2026-07-09",
  "createdAt": "2026-07-09T14:03:20Z"
}
```

<Tip>
  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.
</Tip>

### Request fields

| Field                | Required | Description                                                                                                                             |
| -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `to`                 | Yes      | Destination, E.164 (e.g. `+4915112345678`).                                                                                             |
| `from`               | Yes      | Caller id, E.164. Must be a number your account owns with status `active` — anything else is rejected with `400`.                       |
| `instructions`       | Yes      | What the agent should accomplish, up to 8,000 characters. This is the whole conversation brief: goal, tone, facts it may share, length. |
| `greeting`           |          | The opening line, spoken when the callee answers.                                                                                       |
| `voice`              |          | A voice id from the catalog; unset, calls use Dairo's default conversational English voice. See [Voices](/phone/voices).                |
| `language`           |          | ISO language code, e.g. `de`. Calls default to English.                                                                                 |
| `backgroundAudio`    |          | `silence` (default), `office`, or `none`. Any other value is rejected with `400`.                                                       |
| `maxDurationSeconds` |          | Hard cap on call length, 30–1800 seconds (default 600). Dairo ends the call at the cap.                                                 |
| `record`             |          | Record the call audio. Default `true`; recording consent rules apply in many places.                                                    |
| `variables`          |          | Values for `{{handlebars}}` placeholders in `instructions` and `greeting`.                                                              |
| `metadata`           |          | Opaque key/value object, stored and echoed back on the call.                                                                            |
| `idempotencyKey`     |          | Also accepted as an `Idempotency-Key` header; when both are set they must match.                                                        |

<Warning>
  **`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](/phone/compliance) explains why.
</Warning>

### Fill in per-call values with variables

`instructions` and `greeting` support `{{placeholder}}` syntax, filled from `variables` at call time. Keep the brief as a stable template and inject the per-call facts:

```json theme={null}
{
  "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](/concepts/idempotency) has the general contract.

## Poll the call

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

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl https://api.dairo.app/v1/phone/calls/7c9e6679-7425-40de-963d-04cf43a4b1c3 \
    -H "Authorization: Bearer $DAIRO_API_KEY"
  ```

  ```ts title="TypeScript" theme={null}
  const call = await dairo.calls.get("7c9e6679-7425-40de-963d-04cf43a4b1c3");
  console.log(call.status, call.durationSeconds, call.costUsd);
  ```

  ```python title="Python" theme={null}
  call = dairo.calls.get("7c9e6679-7425-40de-963d-04cf43a4b1c3")
  print(call.status, call.duration_seconds, call.cost_usd)
  ```

  ```bash title="CLI" theme={null}
  dairo phone calls get 7c9e6679-7425-40de-963d-04cf43a4b1c3
  ```

  ```text title="MCP" theme={null}
  Tool: list_phone_calls  (scope phone:read)
  Args: { "action": "get", "callId": "7c9e6679-7425-40de-963d-04cf43a4b1c3" }
  ```
</CodeGroup>

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:

```json theme={null}
{
  "object": "phone_call",
  "id": "7c9e6679-7425-40de-963d-04cf43a4b1c3",
  "status": "completed",
  "fromNumber": "+13125550100",
  "toNumber": "+4915112345678",
  "durationSeconds": 42,
  "summary": "Reached Luka, relayed that the API recovered at 14:02 with all alarms green. He acknowledged and had no questions.",
  "costUsd": 0.14,
  "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:

```bash title="cURL" theme={null}
curl "https://api.dairo.app/v1/phone/calls?status=completed&limit=20" \
  -H "Authorization: Bearer $DAIRO_API_KEY"
```

## 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`.

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl https://api.dairo.app/v1/phone/calls/7c9e6679-7425-40de-963d-04cf43a4b1c3/transcript \
    -H "Authorization: Bearer $DAIRO_API_KEY"
  ```

  ```ts title="TypeScript" theme={null}
  const transcript = await dairo.calls.transcript("7c9e6679-7425-40de-963d-04cf43a4b1c3");
  for (const turn of transcript.turns) console.log(turn.role, turn.content);
  ```

  ```python title="Python" theme={null}
  transcript = dairo.calls.transcript("7c9e6679-7425-40de-963d-04cf43a4b1c3")
  for turn in transcript.turns:
      print(turn.role, turn.content)
  ```

  ```bash title="CLI" theme={null}
  dairo phone calls transcript 7c9e6679-7425-40de-963d-04cf43a4b1c3
  ```

  ```text title="MCP" theme={null}
  Tool: list_phone_calls  (scope phone:read)
  Args: { "action": "transcript", "callId": "7c9e6679-7425-40de-963d-04cf43a4b1c3" }
  ```
</CodeGroup>

```json theme={null}
{
  "object": "phone_call.transcript",
  "callId": "7c9e6679-7425-40de-963d-04cf43a4b1c3",
  "turns": [
    { "role": "agent", "content": "Hallo Luka, 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](/storage/share-links) you can download or share:

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

A call with no recording returns `404` — because it was placed with `record: false`, 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.

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl -X POST https://api.dairo.app/v1/phone/calls/7c9e6679-7425-40de-963d-04cf43a4b1c3/hangup \
    -H "Authorization: Bearer $DAIRO_API_KEY"
  ```

  ```ts title="TypeScript" theme={null}
  const call = await dairo.calls.hangup("7c9e6679-7425-40de-963d-04cf43a4b1c3");
  console.log(call.status); // canceled
  ```

  ```python title="Python" theme={null}
  call = dairo.calls.hangup("7c9e6679-7425-40de-963d-04cf43a4b1c3")
  print(call.status)  # canceled
  ```

  ```bash title="CLI" theme={null}
  dairo phone calls hangup 7c9e6679-7425-40de-963d-04cf43a4b1c3
  ```

  ```text title="MCP" theme={null}
  Tool: manage_phone_calls  (scope phone:call, confirm required)
  Args: { "action": "hangup", "callId": "7c9e6679-7425-40de-963d-04cf43a4b1c3", "confirm": true }
  ```
</CodeGroup>

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`.
