# AI agents

Your AI agents, and one outgoing call through one of them.

## List AI agents

`GET /v1/agents`

Your AI agents, with what you need to pick one for `POST /v1/agents/{id}/calls`. Agents are set up in the portal. An agent can place calls only when `active` and `outgoing_calls` are both true.

Keys with the `agents` scope can call this route.

### Request

**curl**

```bash
curl https://api.vocatech.com/v1/agents \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Node**

```javascript
const res = await fetch("https://api.vocatech.com/v1/agents", {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});

console.log(res.status, await res.json());
```

**Python**

```python
import requests

r = requests.get(
    "https://api.vocatech.com/v1/agents",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    timeout=30,
)
print(r.status_code, r.json())
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.vocatech.com',
    'headers' => ['Authorization' => 'Bearer YOUR_API_KEY'],
    'http_errors' => false,
]);

$res = $client->get('/v1/agents');

echo $res->getStatusCode(), PHP_EOL, $res->getBody(), PHP_EOL;
```

### Answers

#### 200

Your agents.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `agents` | array of objects | yes |  |
| `agents[].id` | integer | yes | Use it with `POST /v1/agents/{id}/calls`. |
| `agents[].name` | string | yes |  |
| `agents[].extension` | string | yes | The agent's extension. |
| `agents[].active` | boolean | yes | The agent is switched on. |
| `agents[].outgoing_calls` | boolean | yes | The agent's Outgoing calls switch is on. It must be on to place calls. |
| `agents[].provisioned` | boolean | yes | The agent is set up on the phone system. |
| `meta` | object | yes |  |
| `meta.total` | integer | yes |  |

**Example**

```json
{
  "agents": [
    {
      "id": 12,
      "name": "Front Desk",
      "extension": "150",
      "active": true,
      "outgoing_calls": true,
      "provisioned": true
    }
  ],
  "meta": {
    "total": 1
  }
}
```

#### 401

No key, or a key that is wrong or turned off.

#### 403

Refused. The key is scoped away from this route, the request came from an address not on the key's allow-list, or the route refused the request (the message says why).

#### 429

A rate limit was reached. Wait `Retry-After` seconds. The body names the counter, the window and the limit.

#### 503

Try again shortly. The key could not be checked just now (`Retry-After: 5`), or a service this route needs could not be reached.

## Place an outgoing AI agent call

`POST /v1/agents/{id}/calls`

Your AI agent places one outgoing call: a reminder, a confirmation, a callback, a notice. The agent opens with its outgoing greeting, says the purpose, and handles the answer with the jobs it has. A job that changes an appointment files the change.

The outcome reaches you the way every message from the agent does: through the agent's message delivery and message action, opening with a tag that names your `reference`.

**Rules.**

- One call per request. This is not a bulk dialer.
- `to` is a 10-digit US or Canadian number, or an extension of 2 to 6 digits. Never an emergency or service number (911 and the other N11 codes, 933, 988), a premium number (900, 976), or an area code that is another country.
- The agent must be on, and its Outgoing calls switch must be on (in the portal, under Persona, Outgoing calls).
- A call outside the agent's own calling hours (8 AM to 9 PM in the agent's time zone unless its Persona tab says otherwise) is refused unless `allow_quiet_hours` is true.
- Send `purpose` or `instructions`, or both.
- An agent places at most 50 calls a day; past that the request is refused (`422`) until the next day.

The answer is `202` once the call is placed. `state` is the first state the phone system reported.

**A retry is safe with an `Idempotency-Key`.** Send the header, your own id for the request (up to 80 characters): a repeat within a day answers the same call (`202`, the same `call_sid`) instead of placing a second one. Without it, the same agent, `to` and `reference` within two minutes is treated as the same request. A `502` means the platform did not answer and the call may have been placed: check for the placed-call tag before you retry, or retry with the same key.

Keys with the `agents` scope can call this route.

### Request

**curl**

```bash
curl -X POST https://api.vocatech.com/v1/agents/12/calls \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "7185550142",
    "purpose": "Confirm the Friday 10 AM appointment",
    "person_name": "Jordan Reyes",
    "reference": "appt-2291",
    "kind": "notice"
  }'
```

**Node**

```javascript
const res = await fetch("https://api.vocatech.com/v1/agents/12/calls", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: "7185550142",
    purpose: "Confirm the Friday 10 AM appointment",
    person_name: "Jordan Reyes",
    reference: "appt-2291",
    kind: "notice",
  }),
});

console.log(res.status, await res.json());
```

**Python**

```python
import requests

r = requests.post(
    "https://api.vocatech.com/v1/agents/12/calls",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "to": "7185550142",
        "purpose": "Confirm the Friday 10 AM appointment",
        "person_name": "Jordan Reyes",
        "reference": "appt-2291",
        "kind": "notice",
    },
    timeout=30,
)
print(r.status_code, r.json())
```

**PHP**

```php
<?php
require 'vendor/autoload.php';

$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.vocatech.com',
    'headers' => ['Authorization' => 'Bearer YOUR_API_KEY'],
    'http_errors' => false,
]);

$res = $client->post('/v1/agents/12/calls', [
    'json' => [
        'to' => '7185550142',
        'purpose' => 'Confirm the Friday 10 AM appointment',
        'person_name' => 'Jordan Reyes',
        'reference' => 'appt-2291',
        'kind' => 'notice',
    ],
]);

echo $res->getStatusCode(), PHP_EOL, $res->getBody(), PHP_EOL;
```

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | integer | yes | The agent's id, from `GET /v1/agents`. For example `12`. |
| `Idempotency-Key` | header | string |  | Your own id for this request, up to 80 characters. A repeat within a day answers the same call instead of placing a second one. Up to 80 characters. For example `visit-20260925-0900`. |

### Body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `to` | string | yes | Who to call: a 10-digit US or Canadian number, or an extension of 2 to 6 digits. Punctuation and a leading 1 are ignored. For example `7185550142`. |
| `purpose` | string |  | Why the agent is calling, in one line. It says this after its greeting. Longer text is cut to 200 characters. Up to 200 characters. |
| `subject` | string |  | Another name for `purpose`. |
| `instructions` | string |  | More for the agent to know or do on this call. Longer text is cut to 2,000 characters. Up to 2,000 characters. |
| `person_name` | string |  | The name of the person being called. Cut to 80 characters. Up to 80 characters. |
| `reference` | string |  | Your own id for this call, echoed in the agent's message about it. Letters, digits, dots, dashes, colons and underscores. Matches `^[A-Za-z0-9._:-]{1,80}$`. |
| `job` | string |  | The name of the agent job the call starts with. Cut to 80 characters. Up to 80 characters. |
| `kind` | string |  | `notice` tells the person something. `callback` answers something they asked for. Any other value reads as `notice`. One of `notice` or `callback`. Default `notice`. |
| `allow_quiet_hours` | boolean |  | Allow a call outside the agent's own calling hours (8 AM to 9 PM in its time zone unless its Persona tab says otherwise). Default `false`. |

**Example**

```json
{
  "to": "7185550142",
  "purpose": "Confirm the Friday 10 AM appointment",
  "person_name": "Jordan Reyes",
  "reference": "appt-2291",
  "kind": "notice"
}
```

### Answers

#### 202

The call is placed.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `call` | object | yes |  |
| `call.call_sid` | string | yes | The id of the placed call. |
| `call.agent_id` | integer | yes |  |
| `call.from` | string | yes | The number the call comes from. |
| `call.to` | string | yes |  |
| `call.state` | string | yes | The first state the phone system reported, for example `trying`. |
| `call.kind` | string | yes | One of `notice` or `callback`. |
| `call.purpose` | string | yes |  |
| `call.reference` | string | yes |  |

**Example**

```json
{
  "call": {
    "call_sid": "c7d2e9a4-1f3b-4c8e-9a6d-0b5e2f7c1a93",
    "agent_id": 12,
    "from": "7185550100",
    "to": "7185550142",
    "state": "trying",
    "kind": "notice",
    "purpose": "Confirm the Friday 10 AM appointment",
    "reference": "appt-2291"
  }
}
```

#### 400

The agent id is not a number.

**Example**

```json
{
  "error": {
    "code": 400,
    "type": "BAD_REQUEST",
    "message": "The agent id must be a number."
  }
}
```

#### 401

No key, or a key that is wrong or turned off.

#### 403

Refused. The key is scoped away from this route, the request came from an address not on the key's allow-list, or the route refused the request (the message says why).

#### 404

No agent with that id on your account.

**Example**

```json
{
  "error": {
    "code": 404,
    "type": "NOT_FOUND",
    "message": "No agent with that id on this account."
  }
}
```

#### 422

A field failed a check, the agent or its outgoing calls are off, it is quiet hours, or the platform refused the call.

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "Outgoing calls are switched off on this agent. Turn them on under Persona, Outgoing calls."
  }
}
```

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "to is required: the number to call, or an extension."
  }
}
```

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "to may not be an emergency or service number."
  }
}
```

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "purpose or instructions is required: the agent has to know why it is calling."
  }
}
```

**Example**

```json
{
  "error": {
    "code": 422,
    "type": "UNPROCESSABLE_ENTITY",
    "message": "This agent is switched off."
  }
}
```

#### 429

The `dial` counter is full (10 a minute, 500 a day per key), or another request limit was reached. Wait `Retry-After` seconds.

**Example**

```json
{
  "error": "Rate limit exceeded",
  "period": "minute",
  "limit": 10,
  "counter": "dial",
  "retry_after": 42
}
```

#### 502

The agent platform did not answer, or answered that it failed. The call may have been placed; check for the placed-call tag before you retry, or retry with the same `Idempotency-Key`.

**Example**

```json
{
  "error": {
    "code": 502,
    "type": "ERROR",
    "message": "The agent platform did not answer. The call may have been placed; check for the placed-call tag before you retry."
  }
}
```

#### 503

Outgoing calls are not available right now, or the key could not be checked.

**Example**

```json
{
  "error": {
    "code": 503,
    "type": "ERROR",
    "message": "Outgoing calls are not available right now."
  }
}
```
