# Overview

What the Alia API does and how access works.

Alia answers WhatsApp messages for real estate agencies. The Alia API lets your CRM read those conversations and send messages to the agencies' contacts.

## What you can do

* See which agencies your key can reach.
* List an agency's WhatsApp conversations, and search them by name or phone number.
* Read the messages in a conversation.
* Send a text message to a contact, even one who has never written to the agency.

The API does not send or upload files, and it does not send webhooks. To pick up new messages, list conversations again: the most recent activity comes first.

## How access works

Alia sets up your CRM as an **integration**. Each agency that uses your CRM with Alia is linked to it.

An API key belongs to one integration. It works for every agency linked to that integration, including agencies linked later. Your key already tells Alia which integration you are, so requests name only the agency, by its ID:

```txt
/agencies/{agencyId}/...
```

Call [`GET /me`](/docs/openapi/me.get) with your key to list your agencies and their IDs.

## Base URL

```txt
https://api.alia.cx/api/v1
```

Every request uses HTTPS. Request and response bodies are JSON.

## Next steps

* [Quickstart](/docs/quickstart): Get a key and make your first calls.
* [Concepts](/docs/concepts): Integrations, agencies, conversations, and the WhatsApp window.
* [API reference](/docs/openapi): Every endpoint, field, and error.

---

# Quickstart

Get an API key, find your agencies, and read your first conversation.

## 1. Get an API key

An owner or admin of your integration creates keys in the [Alia Console](https://console.alia.cx), under **API keys**. If you can't sign in to the Console, ask your Alia contact.

A key looks like `alia_live.<key id>.<secret>`. The Console shows it only once. Keep it in your server's secret store and send it only from your server.

```bash
export ALIA_API_KEY='alia_live.…'
```

## 2. Find your integration and agencies

```bash
curl https://api.alia.cx/api/v1/me \
  -H "Authorization: Bearer $ALIA_API_KEY"
```

```json
{
  "integration": {
    "id": "8f14e45f-ceea-4c6b-9a3e-2d5f1c0b7a91",
    "name": "Acme CRM"
  },
  "agencies": [
    { "id": "agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6", "name": "Immo Dupont" },
    { "id": "agy_5d8e2f0a9c3b4e1d7a6f5c4b3a2e1d0c", "name": "Immo Lambert" }
  ],
  "scopes": ["conversations:read", "messages:write"]
}
```

Every other request names an agency by its `id`. For the rest of this guide, keep the base path for one agency in a variable:

```bash
AGENCY_ID=agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6
export AGENCY_URL="https://api.alia.cx/api/v1/agencies/$AGENCY_ID"
```

## 3. List conversations

```bash
curl "$AGENCY_URL/conversations?pageSize=20" \
  -H "Authorization: Bearer $ALIA_API_KEY"
```

```json
{
  "conversations": [
    {
      "phoneNumber": "+32470123456",
      "contactName": "Marie Peeters",
      "lastMessageAt": "2026-09-30T08:41:12.000Z",
      "lastMessagePreview": "Is the flat on Rue Haute still available?",
      "lastMessageDirection": "inbound",
      "aiEnabled": true,
      "windowExpiresAt": "2026-10-01T08:41:12.000Z"
    }
  ],
  "nextCursor": "eyJsYXN0TWVzc2FnZUF0Ijoi…",
  "pageSize": 20
}
```

We trimmed this response. [List conversations](/docs/openapi/conversations.list) shows every field.

Each conversation is keyed by the contact's phone number, and the most recent activity comes first. When `nextCursor` is not `null`, pass it as `cursor` to get the next page.

## 4. Read messages

Put the phone number in the path in E.164 format, and write the `+` as `%2B`:

```bash
curl "$AGENCY_URL/conversations/%2B32470123456/messages" \
  -H "Authorization: Bearer $ALIA_API_KEY"
```

The newest message comes first, 50 to a page. Page through older messages with `nextCursor`, as in step 3.

## 5. Send a message

> **Warning:** This sends a real WhatsApp message. Send your first one to a phone you
> control.

```bash
curl -X POST "$AGENCY_URL/messages" \
  -H "Authorization: Bearer $ALIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+32470123456",
    "body": "Hello Marie, is Thursday at 10:00 still good for the visit?",
    "operationId": "crm-activity-8841"
  }'
```

If the contact has written to the agency in the last 24 hours, Alia sends the text at once:

```json
{
  "success": true,
  "queued": false,
  "messageId": "SMa1b2c3d4e5f60718293a4b5c6d7e8f90",
  "queueId": null,
  "status": "queued",
  "windowOpen": true,
  "windowExpiresAt": "2026-10-01T08:41:12.000Z"
}
```

If not, Alia holds the text and first asks the contact for consent. The response then has `queued: true`. [Sending messages](/docs/sending-messages) explains both cases, retries, and what a send changes in Alia.

---

# Concepts

Integrations, agencies, keys, conversations, and the WhatsApp window.

## Integrations and agencies

An **integration** is your CRM's account in Alia. An **agency** is a real estate agency that uses Alia. When an agency starts using your CRM with Alia, Alia links the agency to your integration.

Your keys can reach every agency linked to your integration. New agencies appear in [`GET /me`](/docs/openapi/me.get) as soon as they are linked. If a link ends, requests for that agency fail with `403`.

Each agency has an ID, such as `agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6`, which names it in every request. It never changes, so you can store it. Your integration also has an ID, a UUID. You don't send it, because your key already identifies the integration, but quote it when you contact Alia support.

## API keys

Each key belongs to one integration. Keys have two scopes:

| Scope                | Allows                            |
| -------------------- | --------------------------------- |
| `conversations:read` | Listing and reading conversations |
| `messages:write`     | Sending messages                  |

New keys get both scopes. Keys don't expire. When you revoke a key in the Console, requests that use it fail with `401` at once.

Create one key for each system that calls Alia. You can then revoke one key without breaking the others.

## Conversations

A conversation is the WhatsApp thread between one agency and one contact. The API finds a conversation by the contact's phone number.

Send phone numbers in E.164 format, such as `+32470123456`. Alia also accepts Belgian local numbers (`0470 12 34 56`) and the `00` prefix, but E.164 avoids mistakes. In a URL path, write the `+` as `%2B`.

A conversation shows who the contact is, the last message, message counts, whether Alia's AI is on, and when the WhatsApp window closes. [Get a conversation](/docs/openapi/conversations.get) adds the AI settings, any queued message, and whether the agency can message the contact after the window closes.

### Look up several contacts at once

To show WhatsApp status next to a list of contacts, send up to 100 phone numbers in one [Look up conversations](/docs/openapi/conversations.lookup) call, instead of one call per contact. The numbers go in the JSON body, so keep the `+`.

```bash
curl -X POST "https://api.alia.cx/api/v1/agencies/$AGENCY_ID/conversations/lookup" \
  -H "Authorization: Bearer $ALIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phoneNumbers": ["+32470123456", "+32471987654"] }'
```

```json
{
  "results": [
    {
      "phoneNumber": "+32470123456",
      "conversation": {
        "phoneNumber": "+32470123456",
        "contactName": "Marie Peeters",
        "lastMessageAt": "2026-09-30T08:41:12.000Z",
        "windowExpiresAt": "2026-10-01T08:41:12.000Z"
      }
    },
    { "phoneNumber": "+32471987654", "conversation": null }
  ]
}
```

You get one result per number, in the order you sent them. Each `phoneNumber` is the number exactly as you sent it, so you can match results to your contacts. `conversation` is `null` when the agency has no conversation with that number. We trimmed this response: each conversation has the same fields as in [List conversations](/docs/openapi/conversations.list).

## Messages

Each message has a `direction`: `inbound` from the contact, or `outbound` from the agency. Outbound messages include those sent by the AI, by automated flows, by agency staff, and through the API.

`status` is the WhatsApp delivery status, such as `sent`, `delivered`, `read`, or `failed`. `deliveredAt` and `readAt` record when those steps happened.

Contacts can send photos and documents. `media` lists them as WhatsApp provided them, and `mediaStored` lists the copies Alia keeps when the agency stores media. The API can't send files.

## The WhatsApp window

WhatsApp lets a business send free text only within 24 hours of the contact's last message. After that, the business must first send a template that WhatsApp has approved.

`windowExpiresAt` tells you when the current window closes. If that time has passed, or the value is `null`, the window is closed. [Sending messages](/docs/sending-messages) explains what Alia does with a message sent to a closed window.

## The AI and your messages

Alia's AI replies to contacts for the agency, often inside a flow such as a visit booking. When you send a message through the API, Alia treats it as a person taking over. It turns the AI off for that contact and ends any flow in progress, so the AI and your CRM don't both reply.

The AI turns itself back on when the contact next writes more than 24 hours after your message. Agency staff can also turn it back on in Alia. `aiEnabled` on a conversation shows whether the AI is on now.

---

# Sending messages

What happens when you send, how to retry safely, and what can go wrong.

[Send a message](/docs/openapi/messages.send) sends a text to one of an agency's contacts. The contact does not need an existing conversation.

> **Warning:** Every send reaches a real person on WhatsApp. Ask the CRM user to confirm each
> message before you send it.

## The request

```json
{
  "to": "+32470123456",
  "body": "Hello Marie, is Thursday at 10:00 still good for the visit?",
  "operationId": "crm-activity-8841"
}
```

* `to`: the contact's phone number, in E.164 format.
* `body`: plain text.
* `operationId`: your own ID for this message. See [Retries](#retries).
* `consentLanguage`: needed only in some cases. See [When the window is closed](#when-the-window-is-closed).
* `subaccountId`: leave it out. Alia sends from the agency's WhatsApp number.

The key needs the `messages:write` scope.

## When the window is open

If the contact has written to the agency in the last 24 hours, Alia sends your text at once. The response has `queued: false` and the WhatsApp `messageId`.

## When the window is closed

If the contact hasn't written in the last 24 hours, or never has, WhatsApp does not allow free text. Alia then:

1. Holds your text in a queue.
2. Sends the agency's consent template, which asks the contact whether they want to hear from the agency.
3. Sends your text if the contact accepts. If the contact declines, or doesn't answer within the agency's time limit (48 hours unless the agency changed it), Alia drops your text.

The response has `queued: true` and a `queueId`. Tell the CRM user that the message waits for the contact's answer.

Alia holds one text per contact. If you send again while a text waits, the new text replaces it.

Some agencies offer the consent template in more than one language. For those agencies, pass `consentLanguage`, such as `"fr"`. [Get a conversation](/docs/openapi/conversations.get) lists the choices in `windowReopenReadiness.availableLanguages`.

If `windowReopenReadiness.eligibleForManualSend` is `false`, the agency has not set up its consent template, and sends to a closed window fail with `400`.

## Retries

Give each message a unique `operationId`, such as the ID of the CRM activity it belongs to. If a request times out or fails with a `5xx` status, send it again with the same `operationId` and the same body.

* **Window open:** Alia sends the message once, however often you retry.
* **Window closed:** the retry replaces the held text with the same text, so the contact still gets it once.

Without an `operationId`, Alia treats every request as a new message. Never reuse an `operationId` for a different message: the send fails with `500`, and so does every retry.

## What a send changes

A send turns Alia's AI off for that contact and ends any flow in progress. The AI turns itself back on when the contact next writes more than 24 hours later. See [The AI and your messages](/docs/concepts#the-ai-and-your-messages).

## Errors

Error bodies carry a `message` that says what went wrong. See [Errors](/docs/openapi#errors) for the format.

| Status | Common causes                                                                                                                          |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `to` or `body` is missing, the phone number is invalid, `consentLanguage` is missing or unknown, or the agency has no consent template |
| `403`  | The key lacks `messages:write`, or it can't reach this agency                                                                          |
| `409`  | Someone else took over the conversation at the same moment. Read the conversation, then try again                                      |
| `500`  | Alia or WhatsApp could not send the message. Retry with the same `operationId`, and stop after a few tries                             |

---

# Going live

Checks to run before your integration handles real traffic.

There is no test environment. Every key is live, and every send reaches a real WhatsApp user. Work through these checks before launch.

## Keys

* Keep keys in a secret manager on your server. Never put a key in a browser, a mobile app, a URL, or a log.
* Use one key for each system that calls Alia.
* Know how to rotate a key: create the new key, deploy it, then revoke the old one in the Console.

## Reading

* Follow `nextCursor` until it is `null`. Never assume one page holds everything.
* Store each agency's ID from [`GET /me`](/docs/openapi/me.get). Call it again when an agency seems to be missing: Alia adds new agencies to your key automatically.
* URL-encode phone numbers in paths.

## Sending

* Ask the CRM user to confirm each message.
* Send an `operationId` with every message, and reuse it when you retry.
* Handle `queued: true`. Show the user that the message waits for the contact's consent.
* Tell users that a send turns the AI off for that contact.

## Errors and logs

* Log the status code, the error `message`, and the `X-Correlation-Id` response header. Give Alia support the correlation ID when you report a problem.
* Never log keys, phone numbers, or message text.
* Retry `5xx` responses and network errors, waiting longer after each try. Don't retry a `4xx` response until you have changed the request.

## First run

1. Call `GET /me` and check the list of agencies.
2. List one agency's conversations and page through them.
3. Read the messages in one conversation.
4. Send one message to a phone you control. Check that it arrives, and that the AI is now off for that conversation.

---

# Use with AI tools

Give a coding assistant these docs as plain text.

Every page is also available as Markdown. Add `.md` to its URL, as in [`/docs/quickstart.md`](/docs/quickstart.md).

| URL                                              | Contents                                    |
| ------------------------------------------------ | ------------------------------------------- |
| [`/llms.txt`](/llms.txt)                         | A short index of every page                 |
| [`/llms-full.txt`](/llms-full.txt)               | Every page in one file                      |
| [`/openapi/openapi.json`](/openapi/openapi.json) | The OpenAPI contract, for client generators |

Point your assistant at `https://docs.alia.cx/llms-full.txt`, or generate a typed client from the OpenAPI file.

---

# Basics

Base URL, authentication, errors, and pagination for every endpoint.

## Base URL

```txt
https://api.alia.cx/api/v1
```

Every request uses HTTPS. Request and response bodies are JSON, and timestamps are ISO 8601 strings in UTC.

## Authentication

Send your API key as a bearer token on every request:

```txt
Authorization: Bearer alia_live.<key id>.<secret>
```

A missing, revoked, or malformed key gets `401`. A valid key gets `403` when it lacks the scope, or when the agency ID is not one of its agencies. The `403` is the same whether or not that agency exists. See [API keys](/docs/concepts#api-keys).

## Errors

Every error has the same body:

```json
{
  "_tag": "PartnerBadRequest",
  "message": "Invalid query parameter \"pageSize\": expected a value between 1 and 100"
}
```

`_tag` names the error type and `message` explains it. Show or log `message`, but don't match on its text: it may change. Match on the status code or `_tag`.

| Status | `_tag`                 | Meaning                                                    |
| ------ | ---------------------- | ---------------------------------------------------------- |
| `400`  | `PartnerBadRequest`    | The request is invalid. Change it before you retry         |
| `401`  | `PartnerUnauthorized`  | The key is missing, revoked, or malformed                  |
| `403`  | `PartnerForbidden`     | The key can't reach this agency, or lacks the scope        |
| `404`  | `PartnerNotFound`      | The conversation or integration doesn't exist              |
| `409`  | `PartnerConflict`      | Someone changed the conversation at the same moment. Retry |
| `500`  | `PartnerInternalError` | Alia failed. Retry with backoff                            |

Every response carries an `X-Correlation-Id` header. Include it when you contact Alia support.

## Pagination

List endpoints return one page at a time.

* `pageSize` sets the page size: 1 to 100, default 50.
* The response includes `nextCursor`. While it is not `null`, pass it as `cursor` to get the next page.
* Treat cursors as opaque strings. Don't build or edit them, and don't store them for later. An invalid cursor gets `400`.

## Phone numbers

Conversations are keyed by the contact's phone number. Use E.164 format, such as `+32470123456`, and write the `+` as `%2B` in URL paths. See [Conversations](/docs/concepts#conversations).

## Endpoints

**Integration**

- [`GET /me`](/docs/openapi/me.get): Get your integration and agencies

**Conversations**

- [`GET /agencies/{agencyId}/conversations`](/docs/openapi/conversations.list): List conversations
- [`POST /agencies/{agencyId}/conversations/lookup`](/docs/openapi/conversations.lookup): Look up conversations
- [`GET /agencies/{agencyId}/conversations/{phone}`](/docs/openapi/conversations.get): Get a conversation
- [`GET /agencies/{agencyId}/conversations/{phone}/messages`](/docs/openapi/conversations.listMessages): List messages

**Messages**

- [`POST /agencies/{agencyId}/messages`](/docs/openapi/messages.send): Send a message

## OpenAPI file

The contract is available as OpenAPI 3.1 at [`/openapi/openapi.json`](/openapi/openapi.json) and at `https://api.alia.cx/api/v1/openapi.json`.

---

# Get your integration and agencies

Returns the integration your API key belongs to, the agencies it may access and its scopes. Call it first: every other path needs the `id` of one of these agencies.

`GET https://api.alia.cx/api/v1/me`

## Responses

### 200 OK

Your integration, agencies and scopes.

- `integration` (object, required): The integration your API key belongs to.
  - `id` (string, required): Integration id.
  - `name` (string, required): Integration name.
- `agencies` (array of objects, required): Agencies your key may access, sorted by name.
  - `id` (string, required): Agency ID. Use it as `agencyId` in paths. It never changes.
  - `name` (string or null, required): Agency name. Null when the agency has no name set.
- `scopes` (array of strings, required): Actions your key may take.

### Errors

- `401` Unauthorized (`PartnerUnauthorized`): The bearer token is missing or not valid.
- `403` Forbidden (`PartnerForbidden`): The key may not do this.
- `404` Not found (`PartnerNotFound`): Not found.
- `500` Server error (`PartnerInternalError`): Alia failed to handle the request.

Every error body is JSON with the same two fields: `_tag` names the error and `message` explains it. See /docs/openapi#errors.

## Example request

```bash
curl https://api.alia.cx/api/v1/me \
  -H "Authorization: Bearer $ALIA_API_KEY"
```

## Example response (200)

```json
{
  "integration": {
    "id": "8f14e45f-ceea-4c6b-9a3e-2d5f1c0b7a91",
    "name": "Acme CRM"
  },
  "agencies": [
    {
      "id": "agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6",
      "name": "Immo Dupont"
    }
  ],
  "scopes": [
    "conversations:read",
    "messages:write"
  ]
}
```

---

# List conversations

Lists the agency's WhatsApp conversations, newest last message first. Pass `nextCursor` as `cursor` to read the next page; stop when `nextCursor` is null.

`GET https://api.alia.cx/api/v1/agencies/{agencyId}/conversations`

## Path parameters

- `agencyId` (string, required): Agency ID from `GET /me`, such as `agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6`.

## Query parameters

- `pageSize` (integer, optional, min 1, max 100, default 50): Conversations per page, from 1 to 100. Default 50.
- `cursor` (string, optional): `nextCursor` from the previous page. Leave it out to read the first page.
- `search` (string, optional): Filter by contact phone number or name, such as `Marie` or `0470`. Three or more digits match part of a phone number; three or more letters match part of a name.

## Responses

### 200 OK

A page of conversations.

- `conversations` (array of objects, required): Conversations, newest last message first.
  - `phoneNumber` (string, required): Contact phone number in E.164 format.
  - `contactName` (string or null, required): Contact name, when known.
  - `lastMessageAt` (string or null, required): Time of the last message, in either direction.
  - `lastMessagePreview` (string or null, required): Start of the last message text.
  - `lastMessageDirection` (string or null, required): Direction of the last message: `inbound` (from the contact) or `outbound`.
  - `totalMessages` (integer, required): Number of messages in the conversation.
  - `failedMessages` (integer, required): Outbound messages that failed, went undelivered or carry an error code.
  - `activeFlows` (integer, required): Alia flows now running for this contact.
  - `completedFlows` (integer, required): Alia flows this contact has finished.
  - `latestFlowStatus` (string or null, required): Status of the most recently updated flow, such as `active`, `waiting`, `completed`, `cancelled`, `failed` or `timeout`.
  - `lastInboundAt` (string or null, required): Time of the last message from the contact.
  - `windowExpiresAt` (string or null, required): When the WhatsApp 24-hour window closes: 24 hours after the last message from the contact. Null when the contact has never written. After this time a send is queued, not sent.
  - `aiEnabled` (boolean, required): True when the AI may reply to this contact.
  - `queuedMessages` (integer, required): Messages waiting for the contact to reopen the WhatsApp window.
  - `isUnlinked` (boolean, required): True when no agency owns this conversation.
  - `visibility` (object, required): Why this agency can see the conversation.
    - `reason` (string, required): Why the agency sees this conversation. `focus`: the agency owns it. `unlinked`: no agency owns it. `tagged`: the agency sees it through an agency tag. `unknown`: none of these. One of: `focus`, `unlinked`, `tagged`, `unknown`.
    - `isShared` (boolean, required): True when other agencies may also see this conversation.
    - `isVisibleThroughAgencyTag` (boolean, required): True when the agency sees it through an agency tag.
  - `cursor` (string or null, required): Opaque position of this row in the list. Pass it as `cursor` to read the rows after it. Null in lookup results.
- `nextCursor` (string or null, required): Pass as `cursor` to read the next page. Null when there are no more conversations.
- `pageSize` (integer, required): The page size used for this page.

### Errors

- `400` Bad request (`PartnerBadRequest`): The request is not valid.
- `401` Unauthorized (`PartnerUnauthorized`): The bearer token is missing or not valid.
- `403` Forbidden (`PartnerForbidden`): The key may not do this.
- `500` Server error (`PartnerInternalError`): Alia failed to handle the request.

Every error body is JSON with the same two fields: `_tag` names the error and `message` explains it. See /docs/openapi#errors.

## Example request

```bash
curl https://api.alia.cx/api/v1/agencies/agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6/conversations \
  -H "Authorization: Bearer $ALIA_API_KEY"
```

## Example response (200)

```json
{
  "conversations": [
    {
      "phoneNumber": "+32470123456",
      "contactName": "Marie Peeters",
      "lastMessageAt": "2026-09-30T08:41:12.000Z",
      "lastMessagePreview": "Is the flat on Rue Haute still available?",
      "lastMessageDirection": "inbound",
      "totalMessages": 12,
      "failedMessages": 0,
      "activeFlows": 1,
      "completedFlows": 2,
      "latestFlowStatus": "completed",
      "lastInboundAt": "2026-09-30T08:41:12.000Z",
      "windowExpiresAt": "2026-10-01T08:41:12.000Z",
      "aiEnabled": true,
      "queuedMessages": 0,
      "isUnlinked": false,
      "visibility": {
        "reason": "focus",
        "isShared": false,
        "isVisibleThroughAgencyTag": false
      },
      "cursor": "eyJsYXN0TWVzc2FnZUF0IjoiMjAyNi0wMy0xNCJ9"
    }
  ],
  "nextCursor": "eyJsYXN0TWVzc2FnZUF0IjoiMjAyNi0wMy0xNCJ9",
  "pageSize": 50
}
```

---

# Look up conversations

Returns the conversation summary for each phone number you send, up to 100, in the same order. A number with no conversation gets `conversation: null`. Use it to show WhatsApp status next to a list of contacts; read messages with List messages. It only reads, so it needs `conversations:read`.

`POST https://api.alia.cx/api/v1/agencies/{agencyId}/conversations/lookup`

## Path parameters

- `agencyId` (string, required): Agency ID from `GET /me`, such as `agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6`.

## Request body

- `phoneNumbers` (array of strings, required, min items 1, max items 100): Phone numbers to look up, from 1 to 100.

## Responses

### 200 OK

Conversations for the phone numbers you sent.

- `results` (array of objects, required): One result per phone number, in the order you sent them.
  - `phoneNumber` (string, required): The phone number exactly as you sent it.
  - `conversation` (object or null, required): The conversation with this contact. Null when there is none.
    - `phoneNumber` (string, required): Contact phone number in E.164 format.
    - `contactName` (string or null, required): Contact name, when known.
    - `lastMessageAt` (string or null, required): Time of the last message, in either direction.
    - `lastMessagePreview` (string or null, required): Start of the last message text.
    - `lastMessageDirection` (string or null, required): Direction of the last message: `inbound` (from the contact) or `outbound`.
    - `totalMessages` (integer, required): Number of messages in the conversation.
    - `failedMessages` (integer, required): Outbound messages that failed, went undelivered or carry an error code.
    - `activeFlows` (integer, required): Alia flows now running for this contact.
    - `completedFlows` (integer, required): Alia flows this contact has finished.
    - `latestFlowStatus` (string or null, required): Status of the most recently updated flow, such as `active`, `waiting`, `completed`, `cancelled`, `failed` or `timeout`.
    - `lastInboundAt` (string or null, required): Time of the last message from the contact.
    - `windowExpiresAt` (string or null, required): When the WhatsApp 24-hour window closes: 24 hours after the last message from the contact. Null when the contact has never written. After this time a send is queued, not sent.
    - `aiEnabled` (boolean, required): True when the AI may reply to this contact.
    - `queuedMessages` (integer, required): Messages waiting for the contact to reopen the WhatsApp window.
    - `isUnlinked` (boolean, required): True when no agency owns this conversation.
    - `visibility` (object, required): Why this agency can see the conversation.
      - `reason` (string, required): Why the agency sees this conversation. `focus`: the agency owns it. `unlinked`: no agency owns it. `tagged`: the agency sees it through an agency tag. `unknown`: none of these. One of: `focus`, `unlinked`, `tagged`, `unknown`.
      - `isShared` (boolean, required): True when other agencies may also see this conversation.
      - `isVisibleThroughAgencyTag` (boolean, required): True when the agency sees it through an agency tag.
    - `cursor` (string or null, required): Opaque position of this row in the list. Pass it as `cursor` to read the rows after it. Null in lookup results.

### Errors

- `400` Bad request (`PartnerBadRequest`): The request is not valid.
- `401` Unauthorized (`PartnerUnauthorized`): The bearer token is missing or not valid.
- `403` Forbidden (`PartnerForbidden`): The key may not do this.
- `500` Server error (`PartnerInternalError`): Alia failed to handle the request.

Every error body is JSON with the same two fields: `_tag` names the error and `message` explains it. See /docs/openapi#errors.

## Example request

```bash
curl -X POST https://api.alia.cx/api/v1/agencies/agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6/conversations/lookup \
  -H "Authorization: Bearer $ALIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumbers": [
      "+32470123456",
      "+32471987654"
    ]
  }'
```

## Example response (200)

```json
{
  "results": [
    {
      "phoneNumber": "+32470123456",
      "conversation": {
        "phoneNumber": "+32470123456",
        "contactName": "Marie Peeters",
        "lastMessageAt": "2026-09-30T08:41:12.000Z",
        "lastMessagePreview": "Is the flat on Rue Haute still available?",
        "lastMessageDirection": "inbound",
        "totalMessages": 12,
        "failedMessages": 0,
        "activeFlows": 1,
        "completedFlows": 2,
        "latestFlowStatus": "completed",
        "lastInboundAt": "2026-09-30T08:41:12.000Z",
        "windowExpiresAt": "2026-10-01T08:41:12.000Z",
        "aiEnabled": true,
        "queuedMessages": 0,
        "isUnlinked": false,
        "visibility": {
          "reason": "focus",
          "isShared": false,
          "isVisibleThroughAgencyTag": false
        },
        "cursor": null
      }
    },
    {
      "phoneNumber": "+32471987654",
      "conversation": null
    }
  ]
}
```

---

# Get a conversation

Returns one conversation, its AI and window state, any queued text and its newest messages. Use it to check whether a send would go out now or be queued.

`GET https://api.alia.cx/api/v1/agencies/{agencyId}/conversations/{phone}`

## Path parameters

- `agencyId` (string, required): Agency ID from `GET /me`, such as `agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6`.
- `phone` (string, required): Contact phone number in E.164 format.

## Query parameters

- `pageSize` (integer, optional, min 1, max 100, default 50): Messages in the returned message page, from 1 to 100. Default 50.
- `cursor` (string, optional): `nextCursor` from a previous message page, to read older messages. Leave it out for the newest.

## Responses

### 200 OK

A conversation and its newest messages.

- `conversation` (object, required): A conversation with its current state.
  - `summary` (object, required): One WhatsApp conversation with a contact.
    - `phoneNumber` (string, required): Contact phone number in E.164 format.
    - `contactName` (string or null, required): Contact name, when known.
    - `lastMessageAt` (string or null, required): Time of the last message, in either direction.
    - `lastMessagePreview` (string or null, required): Start of the last message text.
    - `lastMessageDirection` (string or null, required): Direction of the last message: `inbound` (from the contact) or `outbound`.
    - `totalMessages` (integer, required): Number of messages in the conversation.
    - `failedMessages` (integer, required): Outbound messages that failed, went undelivered or carry an error code.
    - `activeFlows` (integer, required): Alia flows now running for this contact.
    - `completedFlows` (integer, required): Alia flows this contact has finished.
    - `latestFlowStatus` (string or null, required): Status of the most recently updated flow, such as `active`, `waiting`, `completed`, `cancelled`, `failed` or `timeout`.
    - `lastInboundAt` (string or null, required): Time of the last message from the contact.
    - `windowExpiresAt` (string or null, required): When the WhatsApp 24-hour window closes: 24 hours after the last message from the contact. Null when the contact has never written. After this time a send is queued, not sent.
    - `aiEnabled` (boolean, required): True when the AI may reply to this contact.
    - `queuedMessages` (integer, required): Messages waiting for the contact to reopen the WhatsApp window.
    - `isUnlinked` (boolean, required): True when no agency owns this conversation.
    - `visibility` (object, required): Why this agency can see the conversation.
      - `reason` (string, required): Why the agency sees this conversation. `focus`: the agency owns it. `unlinked`: no agency owns it. `tagged`: the agency sees it through an agency tag. `unknown`: none of these. One of: `focus`, `unlinked`, `tagged`, `unknown`.
      - `isShared` (boolean, required): True when other agencies may also see this conversation.
      - `isVisibleThroughAgencyTag` (boolean, required): True when the agency sees it through an agency tag.
    - `cursor` (string or null, required): Opaque position of this row in the list. Pass it as `cursor` to read the rows after it. Null in lookup results.
  - `queuedMessages` (array of objects, required): The text waiting for the window to reopen, if any. Holds at most one entry.
    - `id` (string, required): Queue entry id.
    - `body` (string, required): Text Alia will send once the contact reopens the window.
    - `attachmentUploadId` (string or null, required): Upload id of an attached file, or null. API sends never attach files.
    - `attachmentFileName` (string or null, required): Name of the attached file.
    - `attachmentMimeType` (string or null, required): MIME type of the attached file.
    - `attachmentFileSize` (integer or null, required): Size of the attached file in bytes.
    - `status` (string, required): Queue status. Always `queued` here.
    - `createdAt` (string, required): When Alia queued the text.
    - `updatedAt` (string, required): When the queued text last changed.
  - `aiSettings` (object, required): AI state for this contact.
    - `enabled` (boolean, required): True when the AI may reply to this contact.
    - `disabledReason` (string or null, required): Why the AI is off: `manual_send` (a person sent a message) or `user_toggle` (agency staff turned it off). After a `manual_send`, the AI turns back on when the contact writes more than 24 hours later.
    - `disabledAt` (string or null, required): When the AI was turned off.
  - `windowReopenReadiness` (object, required): Whether Alia can reach this contact after the 24-hour window closes, by queuing the text and sending a consent template first.
    - `configured` (boolean, required): True when the agency has the consent flow turned on.
    - `templatesApproved` (boolean, required): True when every consent flow and template exists and is approved.
    - `eligibleForManualSend` (boolean, required): True when a send outside the 24-hour window can be queued: templates are approved and at least one language exists.
    - `availableLanguages` (array of strings, required): Languages you may pass as `consentLanguage`.
    - `defaultLanguage` (string or null, required): Language Alia uses by default.
    - `flowCodes` (array of strings, required): Consent flow codes for the agency.
    - `missingFlowCodes` (array of strings, required): Consent flow codes Alia could not find.
    - `missingTemplates` (array of strings, required): Consent templates the agency lacks.
    - `unapprovedTemplates` (array of objects, required): Consent templates waiting for WhatsApp approval.
      - `templateCode` (string, required): Template code.
      - `status` (string or null, required): WhatsApp approval status.
- `messagePage` (object, required): A page of messages, newest first.
  - `messages` (array of objects, required): Messages, newest first.
    - `id` (integer, required): Alia message id.
    - `messageSid` (string, required): Twilio message SID.
    - `body` (string or null, required): Message text. Null for a message with only files.
    - `direction` (string, required): `inbound` (from the contact) or `outbound`.
    - `createdAt` (string or null, required): When the message was sent or received.
    - `deliveredAt` (string or null, required): When WhatsApp reported delivery.
    - `readAt` (string or null, required): When WhatsApp reported the contact read it.
    - `status` (string or null, required): Twilio message status, such as `queued`, `sent`, `delivered`, `read`, `failed`, `undelivered` or, for inbound messages, `received`.
    - `media` (array of objects or null, required): Files the contact sent. Sending files is not supported yet.
      - `url` (string, required): Media link as WhatsApp delivered it.
      - `index` (number, optional): Position of the file in the message.
      - `contentType` (string, optional): MIME type.
      - `content_type` (string, optional): MIME type (older records).
    - `mediaStored` (array of objects or null, required): Copies of inbound files, when the agency has media storage on.
      - `size` (number, required): File size in bytes.
      - `stored_url` (string, required): Link to the copy Alia keeps.
      - `content_type` (string, required): MIME type.
      - `original_url` (string, required): The WhatsApp media link the copy came from.
      - `storage_path` (string, required): Path of the copy in Alia storage.
      - `downloaded_at` (string, required): When Alia stored the copy.
    - `numMedia` (integer or null, required): Number of files in the message.
    - `templateName` (string or null, required): Template code, when Alia sent a template.
    - `buttonClicked` (string or null, required): Code of the button the contact tapped.
    - `profileName` (string or null, required): WhatsApp profile name of the contact.
    - `interactiveContent` (object, optional): Buttons or lists the template showed the contact.
      - `quickReplies` (array of objects or null, optional): Reply buttons.
        - `code` (string, required): Code Alia receives when the contact taps it.
        - `text` (string, required): Button label.
      - `listPicker` (object or null, optional): A list of choices.
        - `buttonText` (string or null, optional): Label of the button that opens the list.
        - `items` (array of objects, required): Choices in the list.
          - `id` (string, required): Item id Alia receives when the contact picks it.
          - `title` (string, required): Item label.
          - `description` (string or null, optional): Extra line under the label.
      - `ctaButtons` (array of objects or null, optional): Call-to-action buttons.
        - `type` (string, required): Button kind, such as `URL` or `PHONE_NUMBER`.
        - `title` (string, required): Button label.
        - `url` (string or null, optional): Link the button opens.
        - `phone` (string or null, optional): Number the button calls.
        - `code` (string or null, optional): Code the button copies.
        - `ttl_minutes` (number or null, optional): Minutes the code stays valid.
    - `replyTo` (object, optional): The message the contact replied to.
      - `body` (string or null, required): Text of the quoted message.
      - `direction` (string or null, required): `inbound` or `outbound`.
      - `createdAt` (string or null, required): When the quoted message was sent.
      - `templateName` (string or null, required): Template code, when the quoted message was a template.
      - `hasMedia` (boolean or null, required): True when the quoted message had files.
  - `nextCursor` (string or null, required): Pass as `cursor` to read older messages. Null when there are no more.
  - `pageSize` (integer, required): The page size used for this page.
  - `hasMore` (boolean, required): True when older messages exist.

### Errors

- `400` Bad request (`PartnerBadRequest`): The request is not valid.
- `401` Unauthorized (`PartnerUnauthorized`): The bearer token is missing or not valid.
- `403` Forbidden (`PartnerForbidden`): The key may not do this.
- `404` Not found (`PartnerNotFound`): Not found.
- `500` Server error (`PartnerInternalError`): Alia failed to handle the request.

Every error body is JSON with the same two fields: `_tag` names the error and `message` explains it. See /docs/openapi#errors.

## Example request

```bash
curl https://api.alia.cx/api/v1/agencies/agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6/conversations/%2B32470123456 \
  -H "Authorization: Bearer $ALIA_API_KEY"
```

## Example response (200)

```json
{
  "conversation": {
    "summary": {
      "phoneNumber": "+32470123456",
      "contactName": "Marie Peeters",
      "lastMessageAt": "2026-09-30T08:41:12.000Z",
      "lastMessagePreview": "Is the flat on Rue Haute still available?",
      "lastMessageDirection": "inbound",
      "totalMessages": 12,
      "failedMessages": 0,
      "activeFlows": 1,
      "completedFlows": 2,
      "latestFlowStatus": "completed",
      "lastInboundAt": "2026-09-30T08:41:12.000Z",
      "windowExpiresAt": "2026-10-01T08:41:12.000Z",
      "aiEnabled": true,
      "queuedMessages": 0,
      "isUnlinked": false,
      "visibility": {
        "reason": "focus",
        "isShared": false,
        "isVisibleThroughAgencyTag": false
      },
      "cursor": "eyJsYXN0TWVzc2FnZUF0IjoiMjAyNi0wMy0xNCJ9"
    },
    "queuedMessages": [
      {
        "id": "3f6c1a52-8d4e-4b7a-9c21-5e0f8a7b6d13",
        "body": "Hello Marie, the visit on Thursday at 10:00 is confirmed.",
        "attachmentUploadId": "5f3a9c2e-1b7d-4e8a-9c6f-2d4b8a1e7c3f",
        "attachmentFileName": "Immo Dupont",
        "attachmentMimeType": "value",
        "attachmentFileSize": 48213,
        "status": "queued",
        "createdAt": "2026-09-30T08:41:12.000Z",
        "updatedAt": "2026-09-30T08:41:12.000Z"
      }
    ],
    "aiSettings": {
      "enabled": true,
      "disabledReason": "manual_send",
      "disabledAt": "2026-09-30T08:41:12.000Z"
    },
    "windowReopenReadiness": {
      "configured": true,
      "templatesApproved": true,
      "eligibleForManualSend": true,
      "availableLanguages": [
        "fr",
        "nl"
      ],
      "defaultLanguage": "fr",
      "flowCodes": [
        "visit"
      ],
      "missingFlowCodes": [
        "visit"
      ],
      "missingTemplates": [
        "first_contact"
      ],
      "unapprovedTemplates": [
        {
          "templateCode": "first_contact",
          "status": "pending"
        }
      ]
    }
  },
  "messagePage": {
    "messages": [
      {
        "id": 987654,
        "messageSid": "SM0123456789abcdef0123456789abcdef",
        "body": "Hello!",
        "direction": "inbound",
        "createdAt": "2026-09-30T08:41:12.000Z",
        "deliveredAt": "2026-09-30T08:41:12.000Z",
        "readAt": "2026-09-30T08:41:12.000Z",
        "status": "delivered",
        "media": [
          {
            "url": "https://api.twilio.com/2010-04-01/Accounts/AC123/Messages/MM123/Media/ME123",
            "index": 0,
            "contentType": "image/jpeg",
            "content_type": "image/jpeg"
          }
        ],
        "mediaStored": [
          {
            "size": 48213,
            "stored_url": "https://storage.example.com/media/abc.jpg",
            "content_type": "image/jpeg",
            "original_url": "https://example.com/media/photo.jpg",
            "storage_path": "agency-123/abc.jpg",
            "downloaded_at": "2026-09-30T08:41:12.000Z"
          }
        ],
        "numMedia": 0,
        "templateName": "first_contact",
        "buttonClicked": "See available times",
        "profileName": "Marie",
        "interactiveContent": {
          "quickReplies": [
            {
              "code": "visit",
              "text": "Yes"
            }
          ],
          "listPicker": {
            "buttonText": "See available times",
            "items": [
              {
                "id": "5f3a9c2e-1b7d-4e8a-9c6f-2d4b8a1e7c3f",
                "title": "Immo Dupont",
                "description": "Pick a time that suits you."
              }
            ]
          },
          "ctaButtons": [
            {
              "type": "URL",
              "title": "Immo Dupont",
              "url": "https://example.com/media/photo.jpg",
              "phone": "+32470123456",
              "code": "visit",
              "ttl_minutes": 1.5
            }
          ]
        },
        "replyTo": {
          "body": "Hello Marie, the visit on Thursday at 10:00 is confirmed.",
          "direction": "outbound",
          "createdAt": "2026-09-30T08:41:12.000Z",
          "templateName": "first_contact",
          "hasMedia": false
        }
      }
    ],
    "nextCursor": "eyJvZmZzZXQiOjI1fQ",
    "pageSize": 50,
    "hasMore": false
  }
}
```

---

# List messages

Lists the messages in one conversation, newest first. Pass `nextCursor` as `cursor` to read older messages; stop when `hasMore` is false.

`GET https://api.alia.cx/api/v1/agencies/{agencyId}/conversations/{phone}/messages`

## Path parameters

- `agencyId` (string, required): Agency ID from `GET /me`, such as `agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6`.
- `phone` (string, required): Contact phone number in E.164 format.

## Query parameters

- `pageSize` (integer, optional, min 1, max 100, default 50): Messages per page, from 1 to 100. Default 50.
- `cursor` (string, optional): `nextCursor` from the previous page. Leave it out to read the newest messages.

## Responses

### 200 OK

A page of messages, newest first.

- `messages` (array of objects, required): Messages, newest first.
  - `id` (integer, required): Alia message id.
  - `messageSid` (string, required): Twilio message SID.
  - `body` (string or null, required): Message text. Null for a message with only files.
  - `direction` (string, required): `inbound` (from the contact) or `outbound`.
  - `createdAt` (string or null, required): When the message was sent or received.
  - `deliveredAt` (string or null, required): When WhatsApp reported delivery.
  - `readAt` (string or null, required): When WhatsApp reported the contact read it.
  - `status` (string or null, required): Twilio message status, such as `queued`, `sent`, `delivered`, `read`, `failed`, `undelivered` or, for inbound messages, `received`.
  - `media` (array of objects or null, required): Files the contact sent. Sending files is not supported yet.
    - `url` (string, required): Media link as WhatsApp delivered it.
    - `index` (number, optional): Position of the file in the message.
    - `contentType` (string, optional): MIME type.
    - `content_type` (string, optional): MIME type (older records).
  - `mediaStored` (array of objects or null, required): Copies of inbound files, when the agency has media storage on.
    - `size` (number, required): File size in bytes.
    - `stored_url` (string, required): Link to the copy Alia keeps.
    - `content_type` (string, required): MIME type.
    - `original_url` (string, required): The WhatsApp media link the copy came from.
    - `storage_path` (string, required): Path of the copy in Alia storage.
    - `downloaded_at` (string, required): When Alia stored the copy.
  - `numMedia` (integer or null, required): Number of files in the message.
  - `templateName` (string or null, required): Template code, when Alia sent a template.
  - `buttonClicked` (string or null, required): Code of the button the contact tapped.
  - `profileName` (string or null, required): WhatsApp profile name of the contact.
  - `interactiveContent` (object, optional): Buttons or lists the template showed the contact.
    - `quickReplies` (array of objects or null, optional): Reply buttons.
      - `code` (string, required): Code Alia receives when the contact taps it.
      - `text` (string, required): Button label.
    - `listPicker` (object or null, optional): A list of choices.
      - `buttonText` (string or null, optional): Label of the button that opens the list.
      - `items` (array of objects, required): Choices in the list.
        - `id` (string, required): Item id Alia receives when the contact picks it.
        - `title` (string, required): Item label.
        - `description` (string or null, optional): Extra line under the label.
    - `ctaButtons` (array of objects or null, optional): Call-to-action buttons.
      - `type` (string, required): Button kind, such as `URL` or `PHONE_NUMBER`.
      - `title` (string, required): Button label.
      - `url` (string or null, optional): Link the button opens.
      - `phone` (string or null, optional): Number the button calls.
      - `code` (string or null, optional): Code the button copies.
      - `ttl_minutes` (number or null, optional): Minutes the code stays valid.
  - `replyTo` (object, optional): The message the contact replied to.
    - `body` (string or null, required): Text of the quoted message.
    - `direction` (string or null, required): `inbound` or `outbound`.
    - `createdAt` (string or null, required): When the quoted message was sent.
    - `templateName` (string or null, required): Template code, when the quoted message was a template.
    - `hasMedia` (boolean or null, required): True when the quoted message had files.
- `nextCursor` (string or null, required): Pass as `cursor` to read older messages. Null when there are no more.
- `pageSize` (integer, required): The page size used for this page.
- `hasMore` (boolean, required): True when older messages exist.

### Errors

- `400` Bad request (`PartnerBadRequest`): The request is not valid.
- `401` Unauthorized (`PartnerUnauthorized`): The bearer token is missing or not valid.
- `403` Forbidden (`PartnerForbidden`): The key may not do this.
- `404` Not found (`PartnerNotFound`): Not found.
- `500` Server error (`PartnerInternalError`): Alia failed to handle the request.

Every error body is JSON with the same two fields: `_tag` names the error and `message` explains it. See /docs/openapi#errors.

## Example request

```bash
curl https://api.alia.cx/api/v1/agencies/agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6/conversations/%2B32470123456/messages \
  -H "Authorization: Bearer $ALIA_API_KEY"
```

## Example response (200)

```json
{
  "messages": [
    {
      "id": 987654,
      "messageSid": "SM0123456789abcdef0123456789abcdef",
      "body": "Hello!",
      "direction": "inbound",
      "createdAt": "2026-09-30T08:41:12.000Z",
      "deliveredAt": "2026-09-30T08:41:12.000Z",
      "readAt": "2026-09-30T08:41:12.000Z",
      "status": "delivered",
      "media": [
        {
          "url": "https://api.twilio.com/2010-04-01/Accounts/AC123/Messages/MM123/Media/ME123",
          "index": 0,
          "contentType": "image/jpeg",
          "content_type": "image/jpeg"
        }
      ],
      "mediaStored": [
        {
          "size": 48213,
          "stored_url": "https://storage.example.com/media/abc.jpg",
          "content_type": "image/jpeg",
          "original_url": "https://example.com/media/photo.jpg",
          "storage_path": "agency-123/abc.jpg",
          "downloaded_at": "2026-09-30T08:41:12.000Z"
        }
      ],
      "numMedia": 0,
      "templateName": "first_contact",
      "buttonClicked": "See available times",
      "profileName": "Marie",
      "interactiveContent": {
        "quickReplies": [
          {
            "code": "visit",
            "text": "Yes"
          }
        ],
        "listPicker": {
          "buttonText": "See available times",
          "items": [
            {
              "id": "5f3a9c2e-1b7d-4e8a-9c6f-2d4b8a1e7c3f",
              "title": "Immo Dupont",
              "description": "Pick a time that suits you."
            }
          ]
        },
        "ctaButtons": [
          {
            "type": "URL",
            "title": "Immo Dupont",
            "url": "https://example.com/media/photo.jpg",
            "phone": "+32470123456",
            "code": "visit",
            "ttl_minutes": 1.5
          }
        ]
      },
      "replyTo": {
        "body": "Hello Marie, the visit on Thursday at 10:00 is confirmed.",
        "direction": "outbound",
        "createdAt": "2026-09-30T08:41:12.000Z",
        "templateName": "first_contact",
        "hasMedia": false
      }
    }
  ],
  "nextCursor": "eyJvZmZzZXQiOjI1fQ",
  "pageSize": 50,
  "hasMore": false
}
```

---

# Send a message

Sends a plain-text WhatsApp message as the agency. If the 24-hour window is closed, Alia queues the text and asks the contact for consent first. A send takes over the conversation and turns the AI off for this contact.

`POST https://api.alia.cx/api/v1/agencies/{agencyId}/messages`

## Path parameters

- `agencyId` (string, required): Agency ID from `GET /me`, such as `agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6`.

## Request body

- `to` (string, required): Contact phone number in E.164 format.
- `body` (string, required): Plain text to send. Files are not supported yet.
- `operationId` (string, optional): Your id for this send. While the window is open, a retry with the same id does not send twice. Reusing an id with a different body fails. Without an id, every request is a new message.
- `subaccountId` (string (uuid) or null, optional): The agency's WhatsApp sender to use. Leave it out to use the agency's default sender.
- `consentLanguage` (string, optional): Language of the consent template, such as `fr`, used only when the window is closed. Required when the agency has more than one consent language. See `windowReopenReadiness.availableLanguages`.

## Responses

### 200 OK

The text was sent or queued.

- `success` (boolean, required): Always true on success.
- `queued` (boolean, required): False: Alia sent the text now. True: the 24-hour window was closed, so Alia queued the text and sent the contact a consent template. Alia sends the text once the contact accepts. A new send while one is queued replaces the queued text.
- `messageId` (string or null, required): Twilio message SID of the sent text. Null when queued.
- `queueId` (string or null, required): Queue entry id. Null when sent now.
- `status` (string or null, required): Twilio status right after sending, such as `queued` or `accepted`. Null when queued or on a retry.
- `windowOpen` (boolean, required): True when the WhatsApp 24-hour window was open.
- `windowExpiresAt` (string or null, required): When the 24-hour window closes. Null when the contact has never written.

### Errors

- `400` Bad request (`PartnerBadRequest`): The request is not valid.
- `401` Unauthorized (`PartnerUnauthorized`): The bearer token is missing or not valid.
- `403` Forbidden (`PartnerForbidden`): The key may not do this.
- `409` Conflict (`PartnerConflict`): The conversation changed during the request.
- `500` Server error (`PartnerInternalError`): Alia failed to handle the request.

Every error body is JSON with the same two fields: `_tag` names the error and `message` explains it. See /docs/openapi#errors.

## Example request

```bash
curl -X POST https://api.alia.cx/api/v1/agencies/agy_9f2c4e1a7b3d4c5e8f60a1b2c3d4e5f6/messages \
  -H "Authorization: Bearer $ALIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+32470123456",
    "body": "Hello Marie, is Thursday at 10:00 still good for the visit?",
    "operationId": "crm-activity-8841"
  }'
```

## Example response (200)

```json
{
  "success": true,
  "queued": false,
  "messageId": "SM0123456789abcdef0123456789abcdef",
  "queueId": "3f6c1a52-8d4e-4b7a-9c21-5e0f8a7b6d13",
  "status": "queued",
  "windowOpen": true,
  "windowExpiresAt": "2026-10-01T08:41:12.000Z"
}
```

---

The complete OpenAPI document is at /openapi/openapi.json.
