# Chats

Chats on your website widget (`channel: web`) and on WhatsApp (`channel: whatsapp`, with the customer's
number in `contact_address`). Reading needs `chats:read`; replying and closing need `chats:write`.

WhatsApp only allows free-form replies within 24 hours of the customer's last message: after that a reply
is refused with `whatsapp_window_closed` (`reply_window_open` tells you beforehand).

Replies you send show in the visitor's widget straight away, from "Team", exactly like a reply typed in
the portal inbox. Replying to a chat the AI is answering takes it over (status `with_owner`), so the AI
stops answering. To build a bot or route chats to your own help desk, subscribe a webhook endpoint to
`chat.message.created` and answer with `POST /chats/{chat}/messages`; skip messages with `via_api: true`,
which are your own.

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

- [List chats](#list-chats): `GET /chats`
- [Get a chat](#get-chat): `GET /chats/{chat}`
- [Reply in a chat](#create-chat-message): `POST /chats/{chat}/messages`
- [Close a chat](#close-chat): `POST /chats/{chat}/close`

## List chats

`GET https://vocenya.com/api/public/v1/chats`

Required scope: `chats:read`

Your website chats, newest first.

### Query parameters

- `status` (string, optional): Who is answering a website chat right now. One of: `ai`, `with_agent`, `with_owner`, `closed`.
- `since` (string (date-time), optional): Chats started at or after this time (ISO 8601).
- `until` (string (date-time), optional): Chats started 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 `ChatResource`
- `401`: The API key is missing, invalid, expired or revoked.
- `403`: The key does not have the `chats:read` 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).
  - `channel` (string, required): Where the chat happens: `web` (the website widget) or `whatsapp`.
  - `contact_address` (string or null, required): The customer's WhatsApp number in E.164 on `whatsapp` chats; `null` on website chats.
  - `reply_window_open` (boolean or null, required): On `whatsapp` chats, whether WhatsApp's 24-hour window for free-form replies is open now.
  - `status` (string, required): `ai` while the AI answers, `with_agent` or `with_owner` after a handoff, then `closed`. One of: `ai`, `with_agent`, `with_owner`, `closed`.
  - `page_url` (string or null, required)
  - `handoff_summary` (string or null, optional): What the AI passed on when it handed the chat to a person. Not included for HIPAA-mode accounts.
  - `handed_off_at` (string or null, required)
  - `closed_at` (string or null, required)
  - `last_message_at` (string or null, required)
  - `lead_id` (integer or null, required): The lead the AI captured in this chat, if any.
  - `messages` (array of objects, optional): The conversation. Only on `GET /chats/{id}`, and never for HIPAA-mode accounts.
    - `id` (integer, required)
    - `livemode` (boolean, required)
    - `sender` (string, required)
    - `role` (string, required): One of: `visitor`, `ai`, `team`, `system`.
    - `author` (string, required)
    - `body` (string, required)
    - `via_api` (boolean, required)
    - `created_at` (string or null, 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/chats \
  -H "Authorization: Bearer $VOCENYA_API_KEY"
```

```javascript
const response = await fetch('https://vocenya.com/api/public/v1/chats', {
  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/chats', [
    '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/chats",
    headers={"Authorization": f"Bearer {os.environ['VOCENYA_API_KEY']}"},
)

print(response.json())
```

### Example response (200)

```json
{
  "data": [
    {
      "id": 1,
      "livemode": true,
      "channel": "web",
      "contact_address": "string",
      "reply_window_open": true,
      "status": "ai",
      "page_url": "https://www.example.com/contact",
      "handoff_summary": "string",
      "handed_off_at": "2026-10-02T14:03:11+00:00",
      "closed_at": "2026-10-02T14:03:11+00:00",
      "last_message_at": "2026-10-02T14:03:11+00:00",
      "lead_id": 1,
      "messages": [
        {
          "id": 1,
          "livemode": true,
          "sender": "string",
          "role": "visitor",
          "author": "string",
          "body": "string",
          "via_api": true,
          "created_at": "2026-10-02T14:03:11+00:00"
        }
      ],
      "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"
  }
}
```


## Get a chat

`GET https://vocenya.com/api/public/v1/chats/{chat}`

Required scope: `chats:read`

One chat with its messages. HIPAA-mode accounts get the chat without messages or handoff summary.

### Path parameters

- `chat` (integer, required): The chat id.

### Responses

- `200`: `ChatResource`
- `401`: The API key is missing, invalid, expired or revoked.
- `403`: The key does not have the `chats:read` 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).
  - `channel` (string, required): Where the chat happens: `web` (the website widget) or `whatsapp`.
  - `contact_address` (string or null, required): The customer's WhatsApp number in E.164 on `whatsapp` chats; `null` on website chats.
  - `reply_window_open` (boolean or null, required): On `whatsapp` chats, whether WhatsApp's 24-hour window for free-form replies is open now.
  - `status` (string, required): `ai` while the AI answers, `with_agent` or `with_owner` after a handoff, then `closed`. One of: `ai`, `with_agent`, `with_owner`, `closed`.
  - `page_url` (string or null, required)
  - `handoff_summary` (string or null, optional): What the AI passed on when it handed the chat to a person. Not included for HIPAA-mode accounts.
  - `handed_off_at` (string or null, required)
  - `closed_at` (string or null, required)
  - `last_message_at` (string or null, required)
  - `lead_id` (integer or null, required): The lead the AI captured in this chat, if any.
  - `messages` (array of objects, optional): The conversation. Only on `GET /chats/{id}`, and never for HIPAA-mode accounts.
    - `id` (integer, required)
    - `livemode` (boolean, required)
    - `sender` (string, required)
    - `role` (string, required): One of: `visitor`, `ai`, `team`, `system`.
    - `author` (string, required)
    - `body` (string, required)
    - `via_api` (boolean, required)
    - `created_at` (string or null, required)
  - `created_at` (string or null, required)

### Example request

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

```javascript
const response = await fetch('https://vocenya.com/api/public/v1/chats/205', {
  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/chats/205', [
    '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/chats/205",
    headers={"Authorization": f"Bearer {os.environ['VOCENYA_API_KEY']}"},
)

print(response.json())
```

### Example response (200)

```json
{
  "data": {
    "id": 1,
    "livemode": true,
    "channel": "web",
    "contact_address": "string",
    "reply_window_open": true,
    "status": "ai",
    "page_url": "https://www.example.com/contact",
    "handoff_summary": "string",
    "handed_off_at": "2026-10-02T14:03:11+00:00",
    "closed_at": "2026-10-02T14:03:11+00:00",
    "last_message_at": "2026-10-02T14:03:11+00:00",
    "lead_id": 1,
    "messages": [
      {
        "id": 1,
        "livemode": true,
        "sender": "string",
        "role": "visitor",
        "author": "string",
        "body": "string",
        "via_api": true,
        "created_at": "2026-10-02T14:03:11+00:00"
      }
    ],
    "created_at": "2026-10-02T14:03:11+00:00"
  }
}
```


## Reply in a chat

`POST https://vocenya.com/api/public/v1/chats/{chat}/messages`

Required scope: `chats:write`

Reply in a live chat as your team. The visitor sees it at once, from "Team". A reply to a chat the AI
is answering takes it over. Limited to 30 replies a minute per key, on top of the overall limit.

```bash
curl -X POST https://vocenya.com/api/public/v1/chats/205/messages \
  -H "Authorization: Bearer $VOCENYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body": "Thanks for waiting! We have an opening on Thursday at 3pm."}'
```

### Path parameters

- `chat` (integer, required): The chat id.

### Request body

- `body` (string, required): The reply, as plain text. The visitor sees it from "Team". Up to 2000 characters.

### Responses

- `201`: The message that was sent.
- `401`: The API key is missing, invalid, expired or revoked.
- `403`: Website live chat is not part of the plan (`live_chat_unavailable`).
- `404`: No record with that id belongs to your account.
- `409`: The chat has ended (`chat_closed`).
- `422`: A WhatsApp chat whose 24-hour reply window has closed (`whatsapp_window_closed`): follow up with an approved template from the inbox.
- `429`: Too many requests for this key. Wait for the `Retry-After` seconds.

### Response attributes (201)

- `data` (object, required)
  - `id` (integer, required)
  - `livemode` (boolean, required): `true` for live data, `false` for test data (created with a `vk_test_` key).
  - `chat_id` (integer, required)
  - `sender` (string, required): Who wrote it: `visitor`, `ai`, `agent` (a GH Live agent), `owner` (the client's team or the API) or `system`.
  - `role` (string, required): What the widget shows: `visitor`, `ai`, `team` or `system`. One of: `visitor`, `ai`, `team`, `system`.
  - `author` (string, required)
  - `body` (string, optional): The text. Not included for HIPAA-mode accounts.
  - `via_api` (boolean, required): Whether it was sent with the public API.
  - `created_at` (string or null, required)

### Example request

```bash
curl -X POST https://vocenya.com/api/public/v1/chats/205/messages \
  -H "Authorization: Bearer $VOCENYA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "body": "Thanks for waiting! We have an opening on Thursday at 3pm. Shall I hold it for you?"
}'
```

```javascript
const response = await fetch('https://vocenya.com/api/public/v1/chats/205/messages', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.VOCENYA_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "body": "Thanks for waiting! We have an opening on Thursday at 3pm. Shall I hold it for you?"
  }),
});

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

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

$response = $client->request('POST', 'https://vocenya.com/api/public/v1/chats/205/messages', [
    'headers' => ['Authorization' => 'Bearer '.getenv('VOCENYA_API_KEY')],
    'json' => [
        'body' => 'Thanks for waiting! We have an opening on Thursday at 3pm. Shall I hold it for you?',
    ],
]);

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

```python
import os

import requests

response = requests.post(
    "https://vocenya.com/api/public/v1/chats/205/messages",
    headers={"Authorization": f"Bearer {os.environ['VOCENYA_API_KEY']}"},
    json={
        "body": "Thanks for waiting! We have an opening on Thursday at 3pm. Shall I hold it for you?",
    },
)

print(response.json())
```

### Example response (201)

```json
{
  "data": {
    "id": 1,
    "livemode": true,
    "chat_id": 205,
    "sender": "string",
    "role": "team",
    "author": "Team",
    "body": "string",
    "via_api": true,
    "created_at": "2026-10-02T14:03:11+00:00"
  }
}
```


## Close a chat

`POST https://vocenya.com/api/public/v1/chats/{chat}/close`

Required scope: `chats:write`

End a chat. The visitor sees that it ended. Closing a chat that already ended changes nothing.

```bash
curl -X POST https://vocenya.com/api/public/v1/chats/205/close \
  -H "Authorization: Bearer $VOCENYA_API_KEY"
```

### Path parameters

- `chat` (integer, required): The chat id.

### Responses

- `200`: `ChatResource`
- `401`: The API key is missing, invalid, expired or revoked.
- `403`: Website live chat is not part of the plan (`live_chat_unavailable`).
- `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).
  - `channel` (string, required): Where the chat happens: `web` (the website widget) or `whatsapp`.
  - `contact_address` (string or null, required): The customer's WhatsApp number in E.164 on `whatsapp` chats; `null` on website chats.
  - `reply_window_open` (boolean or null, required): On `whatsapp` chats, whether WhatsApp's 24-hour window for free-form replies is open now.
  - `status` (string, required): `ai` while the AI answers, `with_agent` or `with_owner` after a handoff, then `closed`. One of: `ai`, `with_agent`, `with_owner`, `closed`.
  - `page_url` (string or null, required)
  - `handoff_summary` (string or null, optional): What the AI passed on when it handed the chat to a person. Not included for HIPAA-mode accounts.
  - `handed_off_at` (string or null, required)
  - `closed_at` (string or null, required)
  - `last_message_at` (string or null, required)
  - `lead_id` (integer or null, required): The lead the AI captured in this chat, if any.
  - `messages` (array of objects, optional): The conversation. Only on `GET /chats/{id}`, and never for HIPAA-mode accounts.
    - `id` (integer, required)
    - `livemode` (boolean, required)
    - `sender` (string, required)
    - `role` (string, required): One of: `visitor`, `ai`, `team`, `system`.
    - `author` (string, required)
    - `body` (string, required)
    - `via_api` (boolean, required)
    - `created_at` (string or null, required)
  - `created_at` (string or null, required)

### Example request

```bash
curl -X POST https://vocenya.com/api/public/v1/chats/205/close \
  -H "Authorization: Bearer $VOCENYA_API_KEY"
```

```javascript
const response = await fetch('https://vocenya.com/api/public/v1/chats/205/close', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.VOCENYA_API_KEY}`,
  },
});

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

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

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

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

```python
import os

import requests

response = requests.post(
    "https://vocenya.com/api/public/v1/chats/205/close",
    headers={"Authorization": f"Bearer {os.environ['VOCENYA_API_KEY']}"},
)

print(response.json())
```

### Example response (200)

```json
{
  "data": {
    "id": 1,
    "livemode": true,
    "channel": "web",
    "contact_address": "string",
    "reply_window_open": true,
    "status": "ai",
    "page_url": "https://www.example.com/contact",
    "handoff_summary": "string",
    "handed_off_at": "2026-10-02T14:03:11+00:00",
    "closed_at": "2026-10-02T14:03:11+00:00",
    "last_message_at": "2026-10-02T14:03:11+00:00",
    "lead_id": 1,
    "messages": [
      {
        "id": 1,
        "livemode": true,
        "sender": "string",
        "role": "visitor",
        "author": "string",
        "body": "string",
        "via_api": true,
        "created_at": "2026-10-02T14:03:11+00:00"
      }
    ],
    "created_at": "2026-10-02T14:03:11+00:00"
  }
}
```

