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

# Create phone call

> Place an outbound phone call with an AI voice agent from one of your phone numbers. Pass dynamic variables to personalize each call.

Places one outbound call. You name the number to call and one of your workspace's [phone numbers](/api-reference/phone-numbers/overview) to call from; Verbeo dials the destination and connects your assistant. Dynamic variables you pass are filled into the assistant's system prompt for this call, so you can personalize each call without creating separate assistants.

<Note>
  This is **version 2** of the endpoint, and the one to build on. It answers the [standard envelope](/api-reference/responses) every other resource uses. [Version 1](/api-reference/calls/initiate) (`POST /calls`) is unchanged and keeps working for integrations built on it.
</Note>

## Endpoint

```
POST https://api.verbeo.ai/v2/call
```

## Body

<ParamField body="to_number" type="string" required>
  The number to call, in E.164 format: `+14155550123`. Spaces, dashes and brackets are accepted and removed. Always send the `+` and the country code. A number without the `+` passes validation and is dialed exactly as sent, but Verbeo's destination rules read it as if it began with `+`: a national number such as `808 555 0123` is then read as a `+808` number and refused with `403`, and whatever is not refused is left to your carrier to read.
</ParamField>

<ParamField body="from_number" type="string" required>
  The number to call from, exactly as it appears in your workspace (`phone_number` on [the phone number object](/api-reference/phone-numbers/overview#the-phone-number-object)). It must be an active number of your workspace.
</ParamField>

<ParamField body="agent_id" type="string">
  UUID of the assistant that handles the call. Optional — leave it out and the call uses the outbound assistant set on `from_number`. If that number has none either, the request is rejected.
</ParamField>

<ParamField body="dynamic_variables" type="object">
  Values for the `{{variables}}` in the assistant's system prompt, by name. Every value must be a string. A name that starts with two underscores (`__`) is reserved: it is ignored, whatever it holds.

  ```json theme={null}
  {
    "customer_name": "Jane Smith",
    "order_id": "ORD-98765"
  }
  ```
</ParamField>

The workspace comes from your API key, so the body names none.

<Note>
  The response returns **immediately**, as soon as the call is created — it does not wait for the destination to answer, and a `201` does not mean the phone rang. A call that then fails to connect (no answer, busy, a carrier error) is reported by its `call_ended` webhook. Track the outcome with the `call_started`, `call_ended` and `call_analyzed` [webhooks](/guides/webhooks): their `call_id` is the `id` this endpoint returns.
</Note>

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.verbeo.ai/v2/call" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "to_number": "+14155550123",
      "from_number": "+14155550100",
      "agent_id": "550e8400-e29b-41d4-a716-446655440000",
      "dynamic_variables": {
        "customer_name": "Jane Smith",
        "order_id": "ORD-98765"
      }
    }'
  ```

  ```bash Use the number's outbound assistant theme={null}
  curl -X POST "https://api.verbeo.ai/v2/call" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "to_number": "+14155550123",
      "from_number": "+14155550100"
    }'
  ```

  ```javascript Node.js theme={null}
  const res = await fetch("https://api.verbeo.ai/v2/call", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.VERBEO_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      to_number: "+14155550123",
      from_number: "+14155550100",
      agent_id: "550e8400-e29b-41d4-a716-446655440000",
      dynamic_variables: {
        customer_name: "Jane Smith",
        order_id: "ORD-98765",
      },
    }),
  });

  const body = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${body.error?.code}: ${body.error?.message}`);
  const call = body.data;
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.post(
      "https://api.verbeo.ai/v2/call",
      headers={"Authorization": f"Bearer {os.environ['VERBEO_API_KEY']}"},
      json={
          "to_number": "+14155550123",
          "from_number": "+14155550100",
          "agent_id": "550e8400-e29b-41d4-a716-446655440000",
          "dynamic_variables": {
              "customer_name": "Jane Smith",
              "order_id": "ORD-98765",
          },
      },
  )
  body = res.json()
  if not res.ok:
      error = body.get("error", {})
      raise RuntimeError(f"{res.status_code} {error.get('code')}: {error.get('message')}")
  call = body["data"]
  ```
</CodeGroup>

## Response

`201 Created` as soon as the call is created.

```json theme={null}
{
  "success": true,
  "data": {
    "id": "b7a3c1e4-94f2-4c9d-a8e0-5d9f6c2a1b34",
    "status": "queued",
    "to_number": "+14155550123",
    "from_number": "+14155550100",
    "agent_id": "550e8400-e29b-41d4-a716-446655440000",
    "agent_name": "Customer Support"
  }
}
```

<ResponseField name="id" type="string" required>
  UUID of the call. It is the `call_id` of the call's [webhook events](/api-reference/webhooks/events).
</ResponseField>

<ResponseField name="status" type="string" required>
  The status the call starts in: `queued` — it has been created and is being placed.
</ResponseField>

<ResponseField name="to_number" type="string" required>
  The number dialed, without the separators you may have sent.
</ResponseField>

<ResponseField name="from_number" type="string" required>
  The number the call is placed from.
</ResponseField>

<ResponseField name="agent_id" type="string" required>
  UUID of the assistant on the call — the one you named, or the number's outbound assistant.
</ResponseField>

<ResponseField name="agent_name" type="string" required>
  That assistant's name.
</ResponseField>

## Errors

Every error uses the [standard error envelope](/api-reference/errors). Branch on `error.code`.

| Code | Status | When |
| - | - | - |
| `VALIDATION_FAILED` | 422 | A missing or malformed field — the message names it, e.g. `to_number: must be a phone number in E.164 format, e.g. +14155550123`. Also: `from_number` is not an active number of your workspace; `agent_id` is not an assistant of your workspace; no `agent_id` was sent and the number has no outbound assistant; or, for a number imported by its Twilio credentials, Twilio refused the call — the message carries its reason. |
| `PAYMENT_REQUIRED` | 402 | Your balance is below what is needed to start a call. Add funds on the **Billing** page. |
| `FORBIDDEN` | 403 | A public (`vbr_pub_…`) key; billing is paused on the workspace; or calls to this destination are not permitted for the workspace. |
| `TOO_MANY_REQUESTS` | 429 | The concurrent-call limit is reached, or the day's spending limit is reached. The concurrent-call limit is your workspace's, counted across all of its calls; for a number imported by its Twilio credentials it is counted on that number's calls alone. For such a number, also: the number is dialing at its calls-per-second limit, or Twilio is rate-limiting your account. |
| `NOT_FOUND` | 404 | The number or the assistant was deleted while the call was being placed. Also a body that names a `workspace_id` other than your key's. |
| `BAD_GATEWAY` | 502 | The request to place the call failed on Verbeo's or the carrier's side, and the call **may or may not** have been created. |
| `INTERNAL_ERROR` | 500 | Unexpected failure before the call was created. |
| `SERVICE_UNAVAILABLE` | 503 | Outbound calling is paused on Verbeo's side. Nothing is wrong with your account. |
| `UNAUTHORIZED` | 401 | Invalid, inactive or expired key. A request with no `Authorization: Bearer vbr_…` header is a `401` too, but [outside the envelope](/api-reference/errors): check the status first. |

### Retrying

| Code | Retry? | Notes |
| - | - | - |
| `TOO_MANY_REQUESTS` | Yes, with a limit | Wait for a call to finish, or a moment for the dial rate, then send the same request. The daily spending limit does not clear until the next UTC day or until it is raised, so cap your retries. |
| `SERVICE_UNAVAILABLE` | Yes | Retry later; calls resume on their own. |
| `BAD_GATEWAY` | Check first | A call may already exist. Look for it in the [call history](/api-reference/calls/list-v2), or for its `call_started` webhook if the assistant has a webhook URL, before you send the request again, or you may call the same person twice. |
| `INTERNAL_ERROR` | Yes | No call was created. Use exponential backoff. |
| `PAYMENT_REQUIRED` | After topping up | The same request goes through once the balance allows it. |
| `VALIDATION_FAILED` | No | Fix the request, then retry. |

<Tip>
  To personalize calls at scale, put placeholders like `{{customer_name}}` in your assistant's system prompt and pass the values through `dynamic_variables` on each call.
</Tip>

## Coming from version 1

<Warning>
  Rename every field. Version 2 reads only its own names and ignores version 1's without an error: a body that still sends `agentId` places the call with the number's outbound assistant instead, and one that still sends `dynamicVariables` places it with no variables.
</Warning>

| | Version 1 — `POST /calls` | Version 2 — `POST /v2/call` |
| - | - | - |
| Body fields | `toNumber`, `fromNumber`, `agentId`, `dynamicVariables` | `to_number`, `from_number`, `agent_id`, `dynamic_variables` |
| Success | `201` with the fields at the top level: `{ "success": true, "callId": "…", … }` | `201` with the call under `data`: `{ "success": true, "data": { "id": "…", … } }` |
| The call's id | `callId` | `data.id` |
| Errors | `{ "error": "…", "details": "…" }` | `{ "success": false, "error": { "code": "…", "message": "…" } }` |
| A bad field | `400` | `422 VALIDATION_FAILED` |
| A number or assistant that is not yours | `404` | `422 VALIDATION_FAILED`, naming the field |
| The concurrent-call limit, for a number imported by its Twilio credentials | Counted across the workspace's calls | Counted on that number's calls |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.