# Outbound calls

Source: https://docs.usetone.ai/outbound-calls

> Placing a call, and reading what happened.

```bash
curl "$TONE_API/v1/calls" -X POST \
  -H "Authorization: Bearer $TONE_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'content-type: application/json' \
  -d "{\"agentId\":\"$AGENT\",\"numberId\":\"$NUMBER\",\"toE164\":\"+919876543210\"}"
```

`agentId` is **required** on a number that routes to a Tone agent and
**forbidden** on a BYO number — the number decides which, and either mistake is a
`422`.

## What happens before the carrier is called

The [compliance gate](/compliance) runs inside the same transaction as the call
record. So a refusal is a `403` with the audit rows already written and no
carrier contacted — the block and its evidence are the same event.

One thing runs *before* the gate: your concurrency quota. Over it, you get
`429 concurrent_call_limit_reached` with no verdict recorded, because no dial was
attempted. That is a different failure from a rate limit and needs a different
response — see [Limits](/limits).

## Lifecycle

| `status` | |
|---|---|
| `queued` | Accepted; the carrier has not connected it yet |
| `in_progress` | Connected |
| `ended` | Finished. `disposition` now says how |

| `disposition` | |
|---|---|
| `answered` | A person picked up |
| `no_answer` | Rang out |
| `busy` | Engaged |
| `voicemail` | An answering machine took it |
| `failed` | The carrier could not complete it |
| `unknown` | We genuinely do not know |

Treat both as **open sets** — new values can appear and are not a breaking
change.

Two honesty rules worth knowing: a call abandoned in `queued` reads as ended
after 15 minutes, and one stuck `in_progress` past 65 minutes reads as ended with
`unknown` and **bills nothing**. We would rather report "we do not know" than
invent an outcome or charge for one.

## Reading it back

Poll if you must, but the call log is not where you should learn a call ended:

```bash
curl "$TONE_API/v1/calls/$CALL" -H "Authorization: Bearer $TONE_KEY"
```

Subscribe to `call.completed` instead. See [Webhooks](/webhooks).

To find every call one campaign placed — **retries included** — filter the call
log rather than reading the recipient list, which names only each recipient's
last attempt:

```bash
curl -H "Authorization: Bearer $TONE_KEY" \
  "$TONE_API/v1/calls?campaign_id=$CAMPAIGN&limit=100"
```

## Test mode

The magic numbers produce fixed outcomes, so CI can assert on them, and nothing
rings. See [Test mode](/test-mode).
