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

# Making Outbound Calls

> Use the API to place a single outbound phone call from one of your agents

Outbound calls let your agents proactively reach out to contacts — for appointment reminders, sales follow-ups, lead qualification, notifications, and more. This guide walks through placing a single call via the API, handling the response, and monitoring call progress.

<Note>
  Need to call hundreds or thousands of contacts? Use [Batch Calls](/guides/batch-calls-and-campaigns-overview) instead. This guide focuses on one-off, programmatically triggered calls.
</Note>

***

## Prerequisites

Before placing an outbound call, verify that:

1. **You have an active agent** — the agent must be published, not in draft. You can confirm this in the dashboard under **Agents**, or by checking `status: "active"` on the agent object.
2. **A phone number is assigned to the agent** — outbound calls are placed from a Truedy number that belongs to your account and is linked to the agent. If no number is assigned, the call will fail immediately with a `no_phone_number_configured` error.
3. **You have a valid API key** — all API requests require `Authorization: Bearer YOUR_API_KEY` in the request headers.

<Tip>
  You can assign a phone number to an agent from the dashboard under **Settings → Phone Numbers**, or programmatically via the phone numbers API.
</Tip>

***

## Placing a Call

Send a `POST` request to `/calls` with the agent and destination phone number.

### Endpoint

```
POST https://api.truedy.ai/api/public/v1/calls
```

### Request Body

| Field           | Type   | Required | Description                                                                                                                                        |
| --------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent_id`      | string | Yes      | The ID of the agent that will handle the call                                                                                                      |
| `phone_number`  | string | Yes      | The destination phone number in E.164 format (e.g. `+14155551234`)                                                                                 |
| `variables`     | object | No       | Key-value pairs for template substitution in the agent's prompt                                                                                    |
| `from_number`   | string | No       | Override the caller ID (must be a number assigned to the agent). If omitted, Truedy picks from the agent's assigned outbound numbers automatically |
| `call_settings` | object | No       | Per-call overrides for recording, greeting, and timeouts (see below)                                                                               |

**`call_settings` fields** (all optional):

| Field                   | Type    | Description                                                         |
| ----------------------- | ------- | ------------------------------------------------------------------- |
| `recording_enabled`     | boolean | Whether to record this call. Default: `true`                        |
| `transcription_enabled` | boolean | Whether to transcribe this call. Default: `true`                    |
| `greeting`              | string  | Override the agent's opening line for this call only                |
| `max_duration`          | string  | Max call duration, e.g. `"1800s"`. Overrides agent default          |
| `join_timeout`          | string  | Timeout for callee to answer, e.g. `"30s"`. Overrides agent default |

#### About `variables`

Your agent's prompt can include template placeholders using double curly braces: `{{first_name}}`, `{{appointment_date}}`, etc. When you pass a `variables` object with the call, Truedy substitutes those values into the prompt before the conversation starts. This lets a single agent handle personalised calls at scale without creating separate agents per contact.

For example, a prompt containing:

```
You are calling {{first_name}} to confirm their appointment on {{appointment_date}} at {{appointment_time}}.
```

...would be rendered with the values you pass in `variables`.

### Examples

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.truedy.ai/api/public/v1/calls \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "agent_id": "agent_01hx2a7b3cd4ef5gh6ij7kl8mn",
      "phone_number": "+14155551234",
      "variables": {
        "first_name": "Sarah",
        "appointment_date": "April 10th",
        "appointment_time": "2:00 PM"
      }
    }'
  ```

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

  response = requests.post(
      "https://api.truedy.ai/api/public/v1/calls",
      headers={
          "Authorization": "Bearer YOUR_API_KEY",
          "Content-Type": "application/json",
      },
      json={
          "agent_id": "agent_01hx2a7b3cd4ef5gh6ij7kl8mn",
          "phone_number": "+14155551234",
          "variables": {
              "first_name": "Sarah",
              "appointment_date": "April 10th",
              "appointment_time": "2:00 PM",
          },
      },
  )

  call = response.json()
  print(call["id"])      # call_01hx3k9p2qw7v8n4m6j5r0y1tz
  print(call["status"])  # queued
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.truedy.ai/api/public/v1/calls", {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      agent_id: "agent_01hx2a7b3cd4ef5gh6ij7kl8mn",
      phone_number: "+14155551234",
      variables: {
        first_name: "Sarah",
        appointment_date: "April 10th",
        appointment_time: "2:00 PM",
      },
    }),
  });

  const call = await response.json();
  console.log(call.id);     // call_01hx3k9p2qw7v8n4m6j5r0y1tz
  console.log(call.status); // queued
  ```
</CodeGroup>

### Response

A successful request returns HTTP `201 Created` with the newly created call object:

```json theme={null}
{
  "id": "call_01hx3k9p2qw7v8n4m6j5r0y1tz",
  "status": "queued",
  "direction": "outbound",
  "agent_id": "agent_01hx2a7b3cd4ef5gh6ij7kl8mn",
  "phone_number": "+14155551234",
  "from_number": "+18005559876",
  "duration_seconds": null,
  "transcript": null,
  "recording_url": null,
  "variables": {
    "first_name": "Sarah",
    "appointment_date": "April 10th",
    "appointment_time": "2:00 PM"
  },
  "created_at": "2024-04-05T14:21:58Z",
  "started_at": null,
  "ended_at": null
}
```

The `id` field is the call identifier you will use to poll for status or match against webhook events.

***

## Monitoring Call Progress

Once you have the call ID, you can track the call to completion using polling or webhooks.

### Option 1: Polling

Call `GET /calls/{id}` repeatedly until the status reaches a terminal state (`ended`, `failed`, or `cancelled`).

<CodeGroup>
  ```python Python theme={null}
  import requests
  import time

  CALL_ID = "call_01hx3k9p2qw7v8n4m6j5r0y1tz"
  HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
  TERMINAL_STATUSES = {"ended", "failed", "cancelled"}

  while True:
      response = requests.get(
          f"https://api.truedy.ai/api/public/v1/calls/{CALL_ID}",
          headers=HEADERS,
      )
      call = response.json()

      print(f"Status: {call['status']}")

      if call["status"] in TERMINAL_STATUSES:
          break

      time.sleep(5)  # wait 5 seconds before checking again

  print(f"Call finished with status: {call['status']}")
  if call["status"] == "ended":
      print(f"Duration: {call['duration_seconds']}s")
      print(f"Outcome: {call['outcome']}")
  ```

  ```javascript JavaScript theme={null}
  const CALL_ID = "call_01hx3k9p2qw7v8n4m6j5r0y1tz";
  const HEADERS = { Authorization: "Bearer YOUR_API_KEY" };
  const TERMINAL_STATUSES = new Set(["ended", "failed", "cancelled"]);

  async function pollCall(callId) {
    while (true) {
      const response = await fetch(
        `https://api.truedy.ai/api/public/v1/calls/${callId}`,
        { headers: HEADERS }
      );
      const call = await response.json();

      console.log(`Status: ${call.status}`);

      if (TERMINAL_STATUSES.has(call.status)) {
        return call;
      }

      await new Promise((resolve) => setTimeout(resolve, 5000));
    }
  }

  const call = await pollCall(CALL_ID);
  console.log(`Call finished: ${call.status}`);
  ```
</CodeGroup>

<Warning>
  Polling is simple but inefficient. Avoid polling intervals shorter than 5 seconds, and prefer webhooks for any integration processing more than a few dozen calls per day.
</Warning>

### Option 2: Webhooks (Recommended)

Register a webhook endpoint and Truedy will deliver a `call.ended` event to your server automatically when the call finishes — no polling required.

See [Webhooks Overview](/guides/webhooks-overview) to set up your endpoint. The `call.ended` event payload includes the full call object, including transcript and recording URL.

***

## Phone Number Format

The `phone_number` field must use **E.164 format**:

* Starts with `+`
* Followed by the country code and subscriber number, no spaces or dashes
* Examples: `+14155551234` (US), `+447911123456` (UK), `+61291234567` (AU)

<Warning>
  Passing a phone number in any other format (e.g. `(415) 555-1234`, `415-555-1234`, or `14155551234` without the leading `+`) will result in a `422 Unprocessable Entity` error.
</Warning>

***

## Common Errors

| HTTP Status                | Error Code                   | Meaning                                   | Resolution                                                     |
| -------------------------- | ---------------------------- | ----------------------------------------- | -------------------------------------------------------------- |
| `404 Not Found`            | `agent_not_found`            | No agent exists with the given `agent_id` | Verify the agent ID in your dashboard                          |
| `422 Unprocessable Entity` | `no_phone_number_configured` | The agent has no outbound number assigned | Assign a phone number to the agent in Settings → Phone Numbers |
| `422 Unprocessable Entity` | `invalid_phone_number`       | `phone_number` is not valid E.164 format  | Reformat the number (e.g. `+14155551234`)                      |
| `422 Unprocessable Entity` | `agent_not_active`           | The agent is in draft or paused state     | Publish the agent before placing calls                         |
| `429 Too Many Requests`    | `rate_limited`               | You've exceeded the API rate limit        | Implement exponential backoff and retry                        |
| `402 Payment Required`     | `insufficient_credits`       | Your account balance is too low           | Top up credits or check your billing plan                      |

***

## Next Steps

<Columns>
  <Card title="Calls Overview" icon="phone" href="/guides/calls-overview">
    Understand call statuses, directions, and lifecycle
  </Card>

  <Card title="Batch Calls & Campaigns" icon="list-check" href="/guides/batch-calls-and-campaigns-overview">
    Call many contacts at once with campaigns
  </Card>

  <Card title="Webhooks Overview" icon="bolt" href="/guides/webhooks-overview">
    Receive real-time events instead of polling
  </Card>

  <Card title="Call Management" icon="magnifying-glass" href="/guides/call-management">
    List, filter, and retrieve call records
  </Card>
</Columns>
