# Outbound calls

Have the AI call someone who agreed to AI calls. Needs `outbound:write`.

Base URL: `https://vocenya.com/api/public/v1`. Authenticate with `Authorization: Bearer $VOCENYA_API_KEY`.

- [List outbound calls](#list-outbound-calls): `GET /outbound-calls`
- [Queue an outbound AI call](#create-outbound-call): `POST /outbound-calls`
- [Get an outbound call](#get-outbound-call): `GET /outbound-calls/{outboundCall}`

## List outbound calls

`GET https://vocenya.com/api/public/v1/outbound-calls`

Required scope: `outbound:write`

Your outbound AI calls, newest first: the ones you queued and those from speed-to-lead,
campaigns and agents.

### Query parameters

- `status` (string, optional): One of: `queued`, `scheduled`, `blocked`, `connecting_agent`, `dialing`, `in_progress`, `completed`, `no_answer`, `voicemail`, `failed`.
- `source` (string, optional): What started the call: `api`, `speed_to_lead`, `campaign` or `agent_dial`. | | |---| | `speed_to_lead` <br/> The AI calls back someone who just asked to be contacted. | | `agent_dial` <br/> A GH Live agent dials a number by hand from the workspace. | | `campaign` <br/> The AI works through a client's campaign contact list. | | `api` <br/> The client's own system queued an AI call through the public API or platform MCP server. | One of: `speed_to_lead`, `agent_dial`, `campaign`, `api`.
- `phone_number` (string, optional): Calls to this number, in any common US format. Up to 32 characters.
- `since` (string (date-time), optional): Calls queued at or after this time (ISO 8601).
- `until` (string (date-time), optional): Calls queued before this time (ISO 8601).
- `per_page` (integer, optional): How many items per page, 1 to 100. Minimum 1. Maximum 100.
- `cursor` (string or null, optional): The `meta.next_cursor` (or `meta.prev_cursor`) of the previous page. Up to 1024 characters.

### Responses

- `200`: Paginated set of `OutboundCallResource`
- `401`: The API key is missing, invalid, expired or revoked.
- `403`: The key does not have the `outbound:write` scope.
- `422`: Validation error
- `429`: Too many requests for this key. Wait for the `Retry-After` seconds.

### Response attributes (200)

- `data` (array of objects, required)
  - `id` (integer, required)
  - `livemode` (boolean, required): `true` for live data, `false` for test data (created with a `vk_test_` key).
  - `source` (string, required): What started the call: your system (`api`), a speed-to-lead callback, a campaign or an agent. | | |---| | `speed_to_lead` <br/> The AI calls back someone who just asked to be contacted. | | `agent_dial` <br/> A GH Live agent dials a number by hand from the workspace. | | `campaign` <br/> The AI works through a client's campaign contact list. | | `api` <br/> The client's own system queued an AI call through the public API or platform MCP server. | One of: `speed_to_lead`, `agent_dial`, `campaign`, `api`.
  - `purpose` (string, required): `marketing` needs marketing consent; `informational` covers reminders and follow-ups the person asked for. One of: `marketing`, `informational`.
  - `status` (string, required): One of: `queued`, `scheduled`, `blocked`, `connecting_agent`, `dialing`, `in_progress`, `completed`, `no_answer`, `voicemail`, `failed`.
  - `contact_name` (string or null, required)
  - `phone_number` (string, required)
  - `blocked_reason` (string or null, required): Why the call was blocked or delayed, as a code.
  - `blocked_reason_message` (string or null, required): Why the call was blocked or delayed, in words.
  - `scheduled_for` (string or null, required): When a delayed call is due to ring (calling hours, daily limit).
  - `attempts` (integer, required)
  - `answered_at` (string or null, required)
  - `answered_by` (string or null, required): Who picked up, from answering machine detection: `human`, `machine` or `unknown`. `null` when there was no result (not answered, or an agent's call). One of: `human`, `machine`, `unknown`.
  - `voicemail_left_at` (string or null, required): When the AI finished leaving a message on voicemail, under the account's "leave a message" setting.
  - `ended_at` (string or null, required)
  - `duration_seconds` (integer or null, required)
  - `summary` (string or null, optional): The AI's summary of the call. Not included for HIPAA-mode accounts.
  - `transcript` (array of objects or null, optional): The conversation, turn by turn. Only on `GET /outbound-calls/{id}`, and never for HIPAA-mode accounts.
    - `role` (string, required)
    - `text` (string, required)
  - `created_at` (string or null, required)
- `links` (object, required)
  - `first` (string or null, required)
  - `last` (string or null, required)
  - `prev` (string or null, required)
  - `next` (string or null, required)
- `meta` (object, required)
  - `path` (string or null, required): Base path for paginator generated URLs.
  - `per_page` (integer, required): Number of items shown per page. Minimum 0.
  - `next_cursor` (string or null, required): The "cursor" that points to the next set of items.
  - `prev_cursor` (string or null, required): The "cursor" that points to the previous set of items.

### Example request

```bash
curl https://vocenya.com/api/public/v1/outbound-calls \
  -H "Authorization: Bearer $VOCENYA_API_KEY"
```

```javascript
const response = await fetch('https://vocenya.com/api/public/v1/outbound-calls', {
  headers: {
    Authorization: `Bearer ${process.env.VOCENYA_API_KEY}`,
  },
});

const data = await response.json();
console.log(data);
```

```php
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://vocenya.com/api/public/v1/outbound-calls', [
    'headers' => ['Authorization' => 'Bearer '.getenv('VOCENYA_API_KEY')],
]);

$data = json_decode((string) $response->getBody(), true);
print_r($data);
```

```python
import os

import requests

response = requests.get(
    "https://vocenya.com/api/public/v1/outbound-calls",
    headers={"Authorization": f"Bearer {os.environ['VOCENYA_API_KEY']}"},
)

print(response.json())
```

### Example response (200)

```json
{
  "data": [
    {
      "id": 1,
      "livemode": true,
      "source": "speed_to_lead",
      "purpose": "marketing",
      "status": "queued",
      "contact_name": "Maria Rivera",
      "phone_number": "+15125550123",
      "blocked_reason": "string",
      "blocked_reason_message": "string",
      "scheduled_for": "string",
      "attempts": 1,
      "answered_at": "2026-10-02T14:03:11+00:00",
      "answered_by": "human",
      "voicemail_left_at": "2026-10-02T14:03:11+00:00",
      "ended_at": "2026-10-02T14:03:11+00:00",
      "duration_seconds": 1,
      "summary": "string",
      "transcript": [
        {
          "role": "string",
          "text": "string"
        }
      ],
      "created_at": "2026-10-02T14:03:11+00:00"
    }
  ],
  "links": {
    "first": "string",
    "last": "string",
    "prev": "string",
    "next": "string"
  },
  "meta": {
    "path": "string",
    "per_page": 1,
    "next_cursor": "string",
    "prev_cursor": "string"
  }
}
```


## Queue an outbound AI call

`POST https://vocenya.com/api/public/v1/outbound-calls`

Required scope: `outbound:write`

Queue an AI call. Every call is checked against the same rules as calls from the portal:
the number must have AI-call consent on record (marketing consent for `marketing` calls) and
must not be on your or GH's Do Not Call list. Those refusals return 422 with the `decision`.

A call that cannot ring right now (outside 8am to 8pm where the number is, three calls in the
last 24 hours, or already on a call) is still accepted: `decision.retry_at` says when it will
ring. Asking again for a number that is still waiting returns the same call.

With a test key (`vk_test_`) the call is checked exactly the same way (against your test Do Not
Call list and test consents), except the free trial's AI minutes, and is then simulated: it never
rings anyone, moves from `queued` to `in_progress` to `completed` with a sample transcript within
a few seconds, and sends `outbound_call.completed` to your test webhook endpoints.

### Request body

- `phone_number` (string, required): The US number to call. It needs AI-call consent on record and must not be on a Do Not Call list. Up to 32 characters.
- `contact_name` (string or null, optional): Up to 255 characters.
- `purpose` (string or null, optional): `marketing` (the default) needs marketing consent. `informational` is for reminders and follow-ups the person asked for, and the only purpose HIPAA-mode accounts may use. One of: `marketing`, `informational`.
- `context` (array of strings or null, optional): Anything the AI should know for the call, like the service they asked about (up to 20 keys). Up to 20 items.

### Responses

- `202`: Accepted. `decision.allowed` is true when the call can ring now; otherwise `decision.retry_at` says when it will.
- `401`: The API key is missing, invalid, expired or revoked.
- `403`: The key does not have the `outbound:write` scope.
- `422`: The call can never be placed: `do_not_call`, `no_consent`, `invalid_number`, `outbound_disabled`, `service_inactive`, `terms_not_accepted`, `hipaa_marketing`, `trial_minutes_exhausted` (the free trial's included AI minutes are used up) or `no_receptionist`.
- `429`: Too many requests for this key. Wait for the `Retry-After` seconds.

### Response attributes (202)

- `data` (object, required)
  - `id` (integer, required)
  - `livemode` (boolean, required): `true` for live data, `false` for test data (created with a `vk_test_` key).
  - `source` (string, required): What started the call: your system (`api`), a speed-to-lead callback, a campaign or an agent. | | |---| | `speed_to_lead` <br/> The AI calls back someone who just asked to be contacted. | | `agent_dial` <br/> A GH Live agent dials a number by hand from the workspace. | | `campaign` <br/> The AI works through a client's campaign contact list. | | `api` <br/> The client's own system queued an AI call through the public API or platform MCP server. | One of: `speed_to_lead`, `agent_dial`, `campaign`, `api`.
  - `purpose` (string, required): `marketing` needs marketing consent; `informational` covers reminders and follow-ups the person asked for. One of: `marketing`, `informational`.
  - `status` (string, required): One of: `queued`, `scheduled`, `blocked`, `connecting_agent`, `dialing`, `in_progress`, `completed`, `no_answer`, `voicemail`, `failed`.
  - `contact_name` (string or null, required)
  - `phone_number` (string, required)
  - `blocked_reason` (string or null, required): Why the call was blocked or delayed, as a code.
  - `blocked_reason_message` (string or null, required): Why the call was blocked or delayed, in words.
  - `scheduled_for` (string or null, required): When a delayed call is due to ring (calling hours, daily limit).
  - `attempts` (integer, required)
  - `answered_at` (string or null, required)
  - `answered_by` (string or null, required): Who picked up, from answering machine detection: `human`, `machine` or `unknown`. `null` when there was no result (not answered, or an agent's call). One of: `human`, `machine`, `unknown`.
  - `voicemail_left_at` (string or null, required): When the AI finished leaving a message on voicemail, under the account's "leave a message" setting.
  - `ended_at` (string or null, required)
  - `duration_seconds` (integer or null, required)
  - `summary` (string or null, optional): The AI's summary of the call. Not included for HIPAA-mode accounts.
  - `transcript` (array of objects or null, optional): The conversation, turn by turn. Only on `GET /outbound-calls/{id}`, and never for HIPAA-mode accounts.
    - `role` (string, required)
    - `text` (string, required)
  - `created_at` (string or null, required)
- `decision` (object, required)
  - `allowed` (boolean, required)
  - `reason` (string or null, required)
  - `message` (string or null, required)
  - `retry_at` (string or null, required)

### Example request

```bash
curl -X POST https://vocenya.com/api/public/v1/outbound-calls \
  -H "Authorization: Bearer $VOCENYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone_number": "+15125550123",
  "contact_name": "Maria Rivera",
  "context": {
    "service": "Water heater replacement",
    "preferred_time": "mornings"
  }
}'
```

```javascript
const response = await fetch('https://vocenya.com/api/public/v1/outbound-calls', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.VOCENYA_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "phone_number": "+15125550123",
    "contact_name": "Maria Rivera",
    "context": {
      "service": "Water heater replacement",
      "preferred_time": "mornings"
    }
  }),
});

const data = await response.json();
console.log(data);
```

```php
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://vocenya.com/api/public/v1/outbound-calls', [
    'headers' => ['Authorization' => 'Bearer '.getenv('VOCENYA_API_KEY')],
    'json' => [
        'phone_number' => '+15125550123',
        'contact_name' => 'Maria Rivera',
        'context' => [
            'service' => 'Water heater replacement',
            'preferred_time' => 'mornings',
        ],
    ],
]);

$data = json_decode((string) $response->getBody(), true);
print_r($data);
```

```python
import os

import requests

response = requests.post(
    "https://vocenya.com/api/public/v1/outbound-calls",
    headers={"Authorization": f"Bearer {os.environ['VOCENYA_API_KEY']}"},
    json={
        "phone_number": "+15125550123",
        "contact_name": "Maria Rivera",
        "context": {
            "service": "Water heater replacement",
            "preferred_time": "mornings",
        },
    },
)

print(response.json())
```

### Example response (202)

```json
{
  "data": {
    "id": 88,
    "source": "api",
    "purpose": "marketing",
    "status": "queued",
    "phone_number": "+15125550123"
  },
  "decision": {
    "allowed": false,
    "reason": "outside_hours",
    "message": "It is outside calling hours (8am to 8pm) where that number is.",
    "retry_at": "2026-09-29T08:00:00-05:00"
  }
}
```


## Get an outbound call

`GET https://vocenya.com/api/public/v1/outbound-calls/{outboundCall}`

Required scope: `outbound:write`

Check on a call you queued: its status, why it was blocked or delayed and, once it ended, its
transcript (not for HIPAA-mode accounts).

### Path parameters

- `outboundCall` (integer, required): The outbound call id.

### Responses

- `200`: `OutboundCallResource`
- `401`: The API key is missing, invalid, expired or revoked.
- `403`: The key does not have the `outbound:write` scope.
- `404`: No record with that id belongs to your account.
- `429`: Too many requests for this key. Wait for the `Retry-After` seconds.

### Response attributes (200)

- `data` (object, required)
  - `id` (integer, required)
  - `livemode` (boolean, required): `true` for live data, `false` for test data (created with a `vk_test_` key).
  - `source` (string, required): What started the call: your system (`api`), a speed-to-lead callback, a campaign or an agent. | | |---| | `speed_to_lead` <br/> The AI calls back someone who just asked to be contacted. | | `agent_dial` <br/> A GH Live agent dials a number by hand from the workspace. | | `campaign` <br/> The AI works through a client's campaign contact list. | | `api` <br/> The client's own system queued an AI call through the public API or platform MCP server. | One of: `speed_to_lead`, `agent_dial`, `campaign`, `api`.
  - `purpose` (string, required): `marketing` needs marketing consent; `informational` covers reminders and follow-ups the person asked for. One of: `marketing`, `informational`.
  - `status` (string, required): One of: `queued`, `scheduled`, `blocked`, `connecting_agent`, `dialing`, `in_progress`, `completed`, `no_answer`, `voicemail`, `failed`.
  - `contact_name` (string or null, required)
  - `phone_number` (string, required)
  - `blocked_reason` (string or null, required): Why the call was blocked or delayed, as a code.
  - `blocked_reason_message` (string or null, required): Why the call was blocked or delayed, in words.
  - `scheduled_for` (string or null, required): When a delayed call is due to ring (calling hours, daily limit).
  - `attempts` (integer, required)
  - `answered_at` (string or null, required)
  - `answered_by` (string or null, required): Who picked up, from answering machine detection: `human`, `machine` or `unknown`. `null` when there was no result (not answered, or an agent's call). One of: `human`, `machine`, `unknown`.
  - `voicemail_left_at` (string or null, required): When the AI finished leaving a message on voicemail, under the account's "leave a message" setting.
  - `ended_at` (string or null, required)
  - `duration_seconds` (integer or null, required)
  - `summary` (string or null, optional): The AI's summary of the call. Not included for HIPAA-mode accounts.
  - `transcript` (array of objects or null, optional): The conversation, turn by turn. Only on `GET /outbound-calls/{id}`, and never for HIPAA-mode accounts.
    - `role` (string, required)
    - `text` (string, required)
  - `created_at` (string or null, required)

### Example request

```bash
curl https://vocenya.com/api/public/v1/outbound-calls/88 \
  -H "Authorization: Bearer $VOCENYA_API_KEY"
```

```javascript
const response = await fetch('https://vocenya.com/api/public/v1/outbound-calls/88', {
  headers: {
    Authorization: `Bearer ${process.env.VOCENYA_API_KEY}`,
  },
});

const data = await response.json();
console.log(data);
```

```php
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://vocenya.com/api/public/v1/outbound-calls/88', [
    'headers' => ['Authorization' => 'Bearer '.getenv('VOCENYA_API_KEY')],
]);

$data = json_decode((string) $response->getBody(), true);
print_r($data);
```

```python
import os

import requests

response = requests.get(
    "https://vocenya.com/api/public/v1/outbound-calls/88",
    headers={"Authorization": f"Bearer {os.environ['VOCENYA_API_KEY']}"},
)

print(response.json())
```

### Example response (200)

```json
{
  "data": {
    "id": 1,
    "livemode": true,
    "source": "speed_to_lead",
    "purpose": "marketing",
    "status": "queued",
    "contact_name": "Maria Rivera",
    "phone_number": "+15125550123",
    "blocked_reason": "string",
    "blocked_reason_message": "string",
    "scheduled_for": "string",
    "attempts": 1,
    "answered_at": "2026-10-02T14:03:11+00:00",
    "answered_by": "human",
    "voicemail_left_at": "2026-10-02T14:03:11+00:00",
    "ended_at": "2026-10-02T14:03:11+00:00",
    "duration_seconds": 1,
    "summary": "string",
    "transcript": [
      {
        "role": "string",
        "text": "string"
      }
    ],
    "created_at": "2026-10-02T14:03:11+00:00"
  }
}
```

