# Place an outbound call (runs the compliance gate)

Source: https://docs.usetone.ai/reference/calls/create

> Runs the full pre-dial compliance gate inside the same transaction, so a refusal is a `403` with the audit rows already written and no carrier contacted.

`POST /v1/calls`
Runs the full pre-dial compliance gate inside the same transaction, so a refusal is a `403` with the audit rows already written and no carrier contacted. Requires `agentId` on a number that routes to a Tone agent and forbids it on a BYO number. Over your concurrent-call quota this returns `429` BEFORE the gate runs, so no verdict is recorded. Send an `Idempotency-Key` — a timeout is otherwise indistinguishable from a call you did not place.

Requires the `write` scope.

## Request body

| Field | Type | | Meaning |
|---|---|---|---|
| `agentId` | string | optional | The agent that runs the call. **Required** when the number routes to a Tone agent (`routingMode: "tone_agent"`) and **forbidden** when it routes to your own stack — the number decides which, and either mistake is a `422`. Optional in the schema only because the schema cannot see the number. |
| `numberId` | string | required | The number to call FROM. Must be `active`; a `suspended` number refuses in both directions. |
| `toE164` | string | required | The recipient, in E.164 — a leading `+`, country code, no spaces or punctuation. In test mode, the magic numbers `+915555000001`-`+915555000006` produce fixed outcomes. |
| `variables` | object | optional | Values for the agent's declared input variables on this call, overriding its defaults. Stored on the call record and returned by reads. Persona data only — nothing here reaches the compliance gate. |

### Example request

```json
{
  "agentId": "9f1c2d84-4e3a-4f6c-b902-7d1e8a33c451",
  "numberId": "9f1c2d84-4e3a-4f6c-b902-7d1e8a33c451",
  "toE164": "+919876543210"
}
```

## Response

| Field | Type | | Meaning |
|---|---|---|---|
| `agent` | object | null | required | Null for a BYO call — the number routes to your own stack, so there is no Tone agent. |
| `agentVersion` | integer | null | required | Which version of the agent ran this call. Pinned when the call was created, so it keeps saying what ran after the agent is edited — which is what makes bucketing outcomes by prompt possible. Null on a BYO call (no Tone agent) and on calls that predate versioning, where "we do not know" is the truthful answer. |
| `billedPaise` | integer | required | What this call actually cost, in paise. One debit per call. |
| `campaignId` | string | null | required | The campaign that placed this call, or null for a call you placed directly. This is how a `call.*` webhook is attributed when several campaigns are running at once — matching on `peer` cannot separate them, and a recipient row only ever names its LAST attempt, so retries have no other handle. |
| `channel` | string | required | How it was carried. Open set — handle an unrecognised value rather than throwing. Today: `browser`, `pstn`. |
| `compliance` | string | null | required | Aggregated posture: `verified` — checks ran and none flagged; `flagged` — one blocked or warned; null — no checks recorded. Per-check evidence is at `GET /v1/compliance/checks?call_id=`. |
| `createdAt` | string | required | RFC 3339, UTC. |
| `direction` | string | required | Which way the call went. Open set — handle an unrecognised value rather than throwing. Today: `inbound`, `outbound`. |
| `disposition` | string | null | required | The outcome. Null until the call ends. Open set — handle an unrecognised value rather than throwing. Today: `answered`, `no_answer`, `busy`, `failed`, `voicemail`, `unknown`. |
| `durationSeconds` | integer | null | required | How long the call was connected, in whole seconds. This is the billed quantity, rounded up to the minimum billable duration. |
| `endReason` | string | null | required | Free-form detail behind the disposition. Diagnostic, not a contract. |
| `endedAt` | string | null | required | When the call finished. RFC 3339, UTC. Null until status is ended. |
| `environment` | string | required | `live` or `test` — inherited from the credential that placed it. |
| `evaluations` | object | null | required | The post-call judge, keyed by your evaluation criteria. Null when it never ran. |
| `hasRecording` | boolean | required | Whether `GET /v1/calls/{id}/recording` will serve audio. |
| `hasTranscript` | boolean | required | Whether `GET /v1/calls/{id}/transcript` will serve anything. |
| `id` | string | required | The call's unique id. |
| `initiator` | object | required | What caused this call to happen. |
| `initiator.id` | string | null | required | Which API key or user, when we know. Null for an inbound call, which presents no credential of yours. |
| `initiator.type` | string | required | Who started it. Open set — handle an unrecognised value rather than throwing. Today: `dashboard`, `api_key`, `inbound`, `sip`. |
| `number` | object | null | required | The Tone number this call used — the one dialled from outbound, the one rung inbound. |
| `outputs` | object | null | required | Structured extraction, keyed by your agent’s declared output variables. A key holding null means "asked, not established" — distinct from the whole field being null. |
| `peer` | string | null | required | The other end in E.164: the callee outbound, the caller inbound. |
| `quality` | object | required | Latency as the caller experienced it. Null on calls that never connected. |
| `quality.answerP50Ms` | integer | null | required | Median time the agent took to produce its first audio for a turn, measured server-side. Lower than `perceivedP50Ms`, which also carries the network and the caller's own pause. |
| `quality.perceivedP50Ms` | integer | null | required | Median gap between the caller finishing and the agent starting to speak. |
| `quality.turns` | integer | null | required | How many times the two sides swapped speaking. A completed call with 0 or 1 turns usually means nobody engaged — an answering machine, or a caller who never spoke. |
| `ratePaisePerMinute` | integer | null | required | The rate this call was billed at, snapshotted when it was created. |
| `startedAt` | string | null | required | When the call connected. RFC 3339, UTC. Null while it is still queued, and on a call that never connected. |
| `status` | string | required | Lifecycle position. `disposition` is only meaningful once this is `ended`. Open set — handle an unrecognised value rather than throwing. Today: `queued`, `in_progress`, `ended`. |
| `summary` | string | null | required | Post-call summary. Null until extraction lands, seconds after the call ends. |
| `variables` | object | null | required | The input-variable map this call ran with. |
| `verdict` | string | required | Strict-AND rollup of `evaluations`. `not_evaluated` is not a failure. Open set — handle an unrecognised value rather than throwing. Today: `success`, `failure`, `unknown`, `not_evaluated`. |

### Example response

```json
{
  "data": {
    "agent": {
      "id": "9f1c2d84-4e3a-4f6c-b902-7d1e8a33c451",
      "name": "Order confirmation"
    },
    "agentVersion": 3,
    "billedPaise": 600,
    "campaignId": null,
    "channel": "pstn",
    "compliance": "passed",
    "createdAt": "2026-08-25T09:14:22Z",
    "direction": "outbound",
    "disposition": "answered",
    "durationSeconds": 45,
    "endReason": "agent_ended",
    "endedAt": "2026-08-25T09:15:11Z",
    "environment": "live",
    "evaluations": {},
    "hasRecording": true,
    "hasTranscript": true,
    "id": "9f1c2d84-4e3a-4f6c-b902-7d1e8a33c451",
    "initiator": {
      "id": "key_8f14a2c9",
      "type": "api_key"
    },
    "number": {
      "e164": "+918045678901",
      "id": "4b7c1a02-9de5-4f31-bb07-2c1a5e9f0d43"
    },
    "outputs": {
      "confirmed": true,
      "delivery_day": "Thursday"
    },
    "peer": "+919876543210",
    "quality": {
      "answerP50Ms": 480,
      "perceivedP50Ms": 0,
      "turns": 6
    },
    "ratePaisePerMinute": 600,
    "startedAt": "2026-08-25T09:14:26Z",
    "status": "ended",
    "summary": "Customer confirmed the order and asked for Thursday delivery.",
    "variables": {
      "customer_name": "Priya",
      "order_id": "AC-4417"
    },
    "verdict": "success"
  }
}
```
