# Place an AI call

`POST /v1/calls`

Asks an existing outbound AI agent to call one lead once.

**Scope:** `calls:write`

**Access:** The key's member needs the Manual call permission in the agent's campaign and must be able to see the lead; creating the lead by `phone` also needs permission to manage that campaign's leads.

The caller owns when to call. The call is placed by the agent you name, in its normal outbound mode with its own first message and prompt, exactly as when the campaign calls a lead; it is not treated as a callback. The agent must be an enabled outbound voice agent in a campaign the key may reach (otherwise `404`), and that campaign is the call's campaign.

Name the lead with `lead_id` (a lead in the agent's campaign) or with `phone`. With `phone`, the campaign's lead with that number is called as it is; if there is none, it is created exactly as `POST /v1/leads` creates it, before the call is checked, so a call that is then refused still leaves the new lead behind. To send more details, create or update the lead first and call it by `lead_id`.

Every call goes through DialEcho's existing automated-call checks, when you send the request and again right before dialing: a number on the do-not-call list or one that cannot be called is refused with `409`, and outside the lead's calling hours the call is accepted as `scheduled` and starts at `scheduled_for` without another request from you. Once a call is requested for a lead, the campaign's own automated dialing leaves that lead alone.

`Idempotency-Key` is required so retries never dial a person twice: DialEcho places at most one call per key. The same request with the same key returns the same `call_id` with the call's current `status`, as long as the key's member may still see the lead; otherwise it is `404 not_found`, as a new request for that lead would be. Derive the key from what the call is for, for example `info-form:<lead_id>`, so one event can never cause two calls. Every request, replays included, also draws from the per-key call limit.

## Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | `string` | Yes | Identifies this call for your API key. Reusing it with a different body is `409 idempotency_conflict`. 16 to 128 printable ASCII characters, no spaces. |

## Body

**Content type:** `application/json`

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `agent_id` | `UUID` | Yes | An enabled outbound voice agent. |
| `lead_id` | `UUID` | No | A lead in the agent's campaign, for example the `lead_id` `POST /v1/leads` returned. Send exactly one of `lead_id` or `phone`. |
| `phone` | `string` | No | Normalized to E.164 like `POST /v1/leads`. Send exactly one of `lead_id` or `phone`. Up to 40 characters. |
| `first_name` | `string` | No | Only with `phone`; used only when the lead is created. Up to 100 characters. |
| `last_name` | `string` | No | Only with `phone`; used only when the lead is created. Up to 100 characters. |
| `email` | `string` | No | Only with `phone`; used only when the lead is created. A valid email address, up to 254 characters. |

## Response

**Status:** `202 Accepted`

A new request always answers `queued` or `scheduled`. The other statuses are what a replay of the same request reports once the call has moved on. Responses never contain the lead's personal data.

| Name | Type | Description |
| --- | --- | --- |
| `call_id` | `UUID` | The call; the same value on every replay. |
| `lead_id` | `UUID` | The lead being called. |
| `status` | `string` | `queued`: starting now (normally within seconds). `scheduled`: outside calling hours, starts at `scheduled_for`. `dialing`: placed. `completed`: answered. `no_answer`: not answered, busy or voicemail; not retried. `failed`: could not be placed (for example the checks before dialing refused it), or DialEcho restarted while starting it and it is never dialed again. `canceled`: canceled in DialEcho before it was placed. One of `queued`, `scheduled`, `dialing`, `completed`, `no_answer`, `failed`, `canceled`. |
| `scheduled_for` | `timestamp` | Only present when `status` is `scheduled`: when the call starts (RFC 3339, UTC). |

## Example request

**curl**

```bash
curl -X POST "https://development.agencydevworks.ai/v1/calls" \
  -H "Authorization: Bearer dei_your_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: info-form:aaaaaaaa-0000-4000-8000-000000000001" \
  -d '{
  "agent_id": "99999999-0000-4000-8000-000000000001",
  "lead_id": "aaaaaaaa-0000-4000-8000-000000000001"
}'
```

**JavaScript**

```js
const response = await fetch("https://development.agencydevworks.ai/v1/calls", {
  method: "POST",
  headers: {
    "Authorization": "Bearer dei_your_key",
    "Content-Type": "application/json",
    "Idempotency-Key": "info-form:aaaaaaaa-0000-4000-8000-000000000001",
  },
  body: JSON.stringify({
    "agent_id": "99999999-0000-4000-8000-000000000001",
    "lead_id": "aaaaaaaa-0000-4000-8000-000000000001"
  }),
});
const data = await response.json();
```

## Example response

**Status:** `202 Accepted`

```json
{
  "call_id": "33333333-0000-4000-8000-000000000001",
  "lead_id": "aaaaaaaa-0000-4000-8000-000000000001",
  "status": "scheduled",
  "scheduled_for": "2026-11-02T14:00:00Z"
}
```

## Errors

| HTTP | Code | When |
| --- | --- | --- |
| 403 | `insufficient_scope` | The key does not have `calls:write`. |
| 403 | `permission_denied` | The key's member may not place AI calls in the agent's campaign, or (with `phone`) may not manage its leads. |
| 404 | `not_found` | The agent does not exist, is not an enabled outbound agent or is outside the key's campaigns, or the lead is not in the agent's campaign or the member cannot see it (a replay of an earlier request included). |
| 409 | `do_not_call` | The number is on the do-not-call list. Final; do not retry. |
| 409 | `invalid_phone` | The lead's number cannot be called. Final; do not retry. |
| 409 | `not_callable` | The lead is outside calling hours and no later calling window can be found. |
| 409 | `idempotency_conflict` | The `Idempotency-Key` was already used for a different request. Nothing was dialed. |
| 413 | `payload_too_large` | The body is larger than 1 MB. It was not read and nothing was dialed. |
| 422 | `invalid_request` | The body or header is invalid: a missing or malformed `Idempotency-Key`, unknown fields, an invalid phone or email, or not exactly one of `lead_id` and `phone` (or names sent with `lead_id`), which is reported as field `body`. |
| 503 | `dependency_unavailable` | The do-not-call list or the campaign's calling-hours settings cannot be read right now. Nothing was placed; retry with the same `Idempotency-Key`. |

Every endpoint can also answer `401 invalid_api_key`, `429 rate_limited` and `503 dependency_unavailable`. See [Errors](/api/#errors) in the overview.

## Related endpoints

- `POST /v1/leads` · [Create or update a lead](/api/leads/create)
- `GET /v1/leads/{lead_id}/conversion` · [Retrieve a conversion](/api/reporting/get-conversion)
