Technical docs

Custom Channel Integration

Overview

Use the private employee message API when an external chat system needs to send user messages to an Alloy AI teammate. This API is intended for custom corporate messengers, internal chat tools, support systems, or middleware that receives messages from another chat provider.

Base URL and auth

Base URL:

```text {api_url}/api/private/employee ```

Authenticate every request with your organization private API key. Send the key in one of these headers:

HeaderFormat
`Authorization``Bearer {private_api_key}`
`api-key``{private_api_key}`

Missing or invalid keys return `401` with `{ "error": "Unauthorized access" }`.

Message identity

Each request must include a `channel` value. Treat it as the stable identifier of your external chat system, for example `corporate-messenger`, `intranet-chat`, or `support-portal`.

For an ongoing chat, also send:

  • `conversation.id`: the chat, room, thread, or dialog ID from your system.
  • `contact.id`: the sender's user ID from your system.

Use the same `channel`, `conversation.id`, and `contact.id` on later requests so Alloy can continue the same chat history.

Send a message

```text POST /api/private/employee/{employee_id}/messages ```

Sends one user message to an AI teammate and waits for the AI response.

`{employee_id}` is the AI teammate ID.

Request body

FieldTypeRequiredNotes
`message`stringyesNon-empty user message text.
`channel`stringyesStable identifier for your external chat system.
`attachments`arraynoDefaults to `[]`. Each item carries base64 file data.
`conversation`objectconditionalRequired together with `contact` for a saved chat. Omit both for a one-off request.
`conversation.id`stringyes, when `conversation` is presentChat, room, thread, or dialog ID from your system.
`conversation.thread_id`string or `null`noOptional subthread ID from your system.
`conversation.title`string, empty string, or `null`noOptional display title for the chat.
`contact`objectconditionalRequired together with `conversation` for a saved chat. Omit both for a one-off request.
`contact.id`stringyes, when `contact` is presentSender's user ID from your system.
`contact.first_name`string, empty string, or `null`noSender first name.
`contact.last_name`string, empty string, or `null`noSender last name.

`conversation` and `contact` are all-or-nothing. Sending only one of them returns `400`.

Attachment item

FieldTypeRequiredNotes
`name`stringyesOriginal file name.
`mime_type`stringyesMIME type.
`size`integeryesFile size in bytes. Must match decoded `data`.
`data`stringyesBase64 encoded file content.

Attachments are saved only for saved chats, meaning requests that include both `conversation` and `contact`. If decoded file size does not match `size`, the request returns `400`.

Saved chat vs one-off request

Use a saved chat when your external system has a conversation that should remain visible and continue over time:

  • include both `conversation` and `contact`
  • Alloy saves the incoming message
  • Alloy can reuse the same chat on later calls when you send the same identifiers
  • the response includes the saved incoming `messageId`

Use a one-off request when you only need an immediate answer and do not want to save chat history:

  • omit both `conversation` and `contact`
  • Alloy returns an AI response for that single message
  • the response has `messageId: null`

Response

```json { "success": true, "data": { "runId": "workflow-run-id", "message": "AI teammate response", "messageId": "saved-incoming-message-id-or-null" } } ```

FieldDescription
`data.runId`ID of the processing run for this request.
`data.message`AI teammate response.
`data.messageId`ID of the saved incoming message, or `null` for one-off requests.

Errors

StatusBodyMeaning
`400``{ "error": "..." }`Invalid request body or attachment size mismatch.
`401``{ "error": "Unauthorized access" }`Missing or invalid private API key.
`404``{ "error": "AI employee not found" }`The AI teammate ID does not exist in the authenticated organization.
`422``{ "error": "AI employee has no runnable workflow" }`The AI teammate is not ready to process messages.

Example: one-off request

```bash curl -X POST "{api_url}/api/private/employee/{employee_id}/messages" -H "Authorization: Bearer {private_api_key}" -H "Content-Type: application/json" -d '{ "channel": "corporate-messenger", "message": "What is our PTO policy?" }' ```

Example: saved chat

```bash curl -X POST "{api_url}/api/private/employee/{employee_id}/messages" -H "Authorization: Bearer {private_api_key}" -H "Content-Type: application/json" -d '{ "channel": "corporate-messenger", "message": "I need help with invoice 123", "conversation": { "id": "room-42", "thread_id": null, "title": "Finance support" }, "contact": { "id": "user-777", "first_name": "Alex", "last_name": "Customer" } }' ```

Example: saved chat with attachment

```bash curl -X POST "{api_url}/api/private/employee/{employee_id}/messages" -H "api-key: {private_api_key}" -H "Content-Type: application/json" -d '{ "channel": "corporate-messenger", "message": "Here is the screenshot", "conversation": { "id": "room-42" }, "contact": { "id": "user-777" }, "attachments": [ { "name": "screenshot.png", "mime_type": "image/png", "size": 20482, "data": "iVBORw0KGgo..." } ] }' ```

List messages for a saved chat

```text GET /api/private/employee/{employee_id}/messages ```

Returns saved messages for a conversation previously used with `POST`.

Query parameters

ParameterTypeRequiredDefaultNotes
`conversation_id`stringyesnoneThe same value you sent as `conversation.id`.
`channel`stringyesnoneThe same value you sent as `channel`.
`page`integerno`1`Minimum `1`.
`limit`integerno`30`Minimum `1`, maximum `50`.

Response

```json { "success": true, "data": [ { "id": "message-id", "conversationId": "conversation-id", "threadId": null, "content": "I need help with invoice 123", "type": "text", "sender": "customer", "status": "sent", "timestamp": 1780000000000 } ], "pagination": { "page": 1, "limit": 30, "total": 1, "totalPages": 1, "has_more": false } } ```

Message objects can include additional fields such as attachments, reply references, reaction state, sender display name, and timestamps.

Errors

StatusBodyMeaning
`400``{ "error": "..." }`Missing or invalid query parameters.
`401``{ "error": "Unauthorized access" }`Missing or invalid private API key.
`404``{ "error": "Conversation not found" }`No saved chat exists for that `channel` and `conversation_id`.

Example

```bash curl "{api_url}/api/private/employee/{employee_id}/messages?channel=corporate-messenger&conversation_id=room-42&page=1&limit=30" -H "api-key: {private_api_key}" ```

Integration pattern

A custom messenger bridge usually does this:

  1. Receive a user message from the external chat system.
  2. Convert the source chat system name to a stable `channel` value.
  3. Convert the external chat ID to `conversation.id`.
  4. Convert the external sender ID and name to `contact`.
  5. Call `POST /api/private/employee/{employee_id}/messages`.
  6. Send `data.message` from the Alloy response back to the external chat system.
  7. Use `GET /api/private/employee/{employee_id}/messages` only when your integration needs to reload saved history.

This API is synchronous. The response body is the delivery path back to the external chat system; this route does not register an outbound webhook for custom channels.

Start building your AI team