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

# POST /v1/calls

> Ring someone into one of your IVRs, or have a voice agent call them.

Rings someone into one of your IVRs, or has a voice agent call them. It is screened like any message: refused with `not_permitted` when their consent doesn't allow it. The answer is the call as it starts (`dialling`); it moves on as it is answered and ends — read it back, or follow the `call.*` webhook events.

<Note>Needs the `calls:write` scope.</Note>

## Body

| Field | Type | Required | Notes |
| :- | :- | :- | :- |
| `target` | object | Yes | `{"type": "ivr" or "voice_agent", "id"}`, from [GET /v1/calls/targets](/api-reference/calls/targets). |
| `to` | string | One of `to` or `contact` | The number to call, with its country code. |
| `contact` | string | One of `to` or `contact` | A contact's id; their main number is called unless `to` is given. |
| `variables` | object | No | Names and values the IVR or agent can use. A contact's `name` is added unless you give one. |
| `purpose` | string | No | `transactional` (the default) or `marketing`. |

Send an `Idempotency-Key` header to make a retry safe: the same key with the same body is answered once, and the first answer repeated (with `Idempotent-Replayed: true`).

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.fireflo.au/v1/calls \
    -H "Authorization: Bearer $OMNI_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: call-a1042-confirm" \
    -d '{
      "to": "+919876543210",
      "target": {
        "type": "ivr",
        "id": "4471"
      },
      "variables": {
        "order": "A-1042"
      }
    }'
  ```

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

  import requests

  response = requests.post(
      "https://api.fireflo.au/v1/calls",
      headers={
          "Authorization": f"Bearer {os.environ['OMNI_API_KEY']}",
          "Idempotency-Key": "call-a1042-confirm",
      },
      json={
          "to": "+919876543210",
          "target": {
              "type": "ivr",
              "id": "4471",
          },
          "variables": {
              "order": "A-1042",
          },
      },
  )
  print(response.status_code, response.json())
  ```

  ```javascript Node theme={null}
  const response = await fetch("https://api.fireflo.au/v1/calls", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OMNI_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": "call-a1042-confirm",
    },
    body: JSON.stringify({
      "to": "+919876543210",
      "target": {
        "type": "ivr",
        "id": "4471"
      },
      "variables": {
        "order": "A-1042"
      }
    }),
  });
  console.log(response.status, await response.json());
  ```
</CodeGroup>

## Response

`202 Accepted`

```json theme={null}
{
  "id": "omni-6f1c2e9a0b7d4c3e8a5f2b1d9c7e4a60",
  "kind": "ivr",
  "direction": "outbound",
  "to": "+919876543210",
  "number": "+919876543210",
  "caller_id": "+911140005678",
  "did": "",
  "contact": {
    "uuid": "3f6b2a1e-8c4d-4e2a-9b7f-5d1c0e8a9f42",
    "name": "Asha Rao"
  },
  "ivr": "Delivery confirmation",
  "agent": "",
  "placed_by": "",
  "status": "dialling",
  "reason": "",
  "seconds": null,
  "recorded": false,
  "summary": "",
  "created": "2026-10-05T09:14:22.413Z",
  "started_at": null,
  "answered_at": null,
  "ended_at": null
}
```

### The call

| Field | Notes |
| :- | :- |
| `id` | The call's id. |
| `kind` | `ivr` (rung into an IVR), `ai` (a voice agent), `inbound`, `phone` (dialled from a desk or app phone), `click` (click-to-call) or `broadcast`. |
| `direction` | `inbound` or `outbound`. |
| `to`, `number` | The number rung, and the other party's number. |
| `caller_id`, `did` | The number an outgoing call showed, and the number an incoming call rang. |
| `contact` | `{uuid, name}`, or null. |
| `ivr`, `agent`, `placed_by` | The IVR it went into, the agent who took it, and the team member who placed it. |
| `status` | `dialling`, `started` (in progress), `ended`, `failed` (not answered) or `missed` (rang in, nobody took it). |
| `reason` | Why it failed. |
| `seconds`, `recorded`, `summary` | How long it lasted, whether it was recorded, and a voice agent's summary. |
| `created`, `started_at`, `answered_at`, `ended_at` | When each happened, or null. |

## Errors

Every refusal is `{"error": {"code", "message", "field"}}`; `field` is there when one input is at fault.

| Status | Error code | When |
| :- | :- | :- |
| 400 | `invalid_request` | `target` is missing or unknown, or `contact`, `to`, `variables` or `purpose` is wrong (`field` names it). |
| 409 | `calls_unavailable` | The account has no Voice. |
| 409 | `not_permitted` | The person's consent doesn't allow the call. |
| 409 | `call_refused` | Voice couldn't place the call; the message says why. |
| 400 | `invalid_json` | The body isn't valid JSON. |
| 403 | `scope_missing` | The key doesn't have the `calls:write` scope. |

Any request can also be refused for its key, its account or its rate (`key_required`, `invalid_key`, `account_suspended`, `plan_excludes_api`, `address_not_allowed`, `rate_limited`); see [the overview](/api-reference/overview).


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