Chat
The Chat endpoint receives a message through an API inbox, records the conversation in SquadOS, and normally runs the assigned agent. The response can return in the same request or be delivered later to a webhook.
POST /chat/{id}
Section titled “POST /chat/{id}”Prefer the API inbox ID for {id}. For backward compatibility, the endpoint also accepts the ID of an agent that has an active API inbox.
curl -X POST https://api.squados.io/v1/chat/API_INBOX_ID \ -H "Authorization: Bearer pk_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "message": "What are your opening hours?", "sync": true }'The token and inbox must belong to the same organization. An inactive inbox, an inbox for another channel, or an inbox from another organization is rejected.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
message | string | Conditional | Message text. Send text, at least one attachment, or both. |
role | user | assistant | No | Default: user. With assistant, the message is only stored in history; the agent does not run. |
conversation_id | string (UUID) | No | Continues a specific conversation in the organization. Takes precedence over external_user_id. |
external_user_id | string | No | Stable contact identifier from your system. Without conversation_id, reuses the latest conversation for this identifier in the organization. |
user_name | string | No | Contact name. Updates the conversation name and, when a contact is linked, the name shown in Conversations. |
phone | string | No | Contact phone number. Saved to the person’s record when the number does not already belong to another contact. Any punctuation works: +55 (11) 99999-8888 and 5511999998888 are the same number. |
email | string | No | Contact email, with the same rule as phone. |
sync | boolean | No | With true, returns the agent response in the same call. With false and webhook_url, queues asynchronous processing. |
webhook_url | string (HTTP/HTTPS URL) | No | Asynchronous callback destination. Without this URL, the response is direct. With sync: true, the response remains direct and no callback is sent. |
attachments | array of Attachment | Conditional | Images, audio, or files. Can replace message in an attachment-only request. |
metadata | object | No | Free-form correlation data. It is also returned in the asynchronous callback. |
onlyStorage | boolean | No | With true, stores the message without running the agent. Useful for syncing external history. |
Attachment fields
Section titled “Attachment fields”| Field | Type | Required | Description |
|---|---|---|---|
name | string | Recommended | File name. When omitted, SquadOS uses attachment. |
url | string | Yes | An HTTP/HTTPS URL accessible to SquadOS or a complete data URL such as data:image/png;base64,.... Raw base64 without the data: prefix is not accepted. |
type | image | audio | file | Recommended | Attachment category. If omitted or invalid, SquadOS tries to infer it from mimeType; video is treated as file. |
mimeType | string | Recommended | MIME type such as image/jpeg, audio/mpeg, or application/pdf. |
Actual processing depends on the agent’s capabilities and settings. For example, audio can be transcribed and file text can be extracted before reaching the model.
Conversation continuity
Section titled “Conversation continuity”SquadOS resolves the conversation in this order:
- If
conversation_idis provided, continue that conversation if it belongs to the token’s organization. - Without
conversation_id, ifexternal_user_idis provided, find the latest conversation for that identifier in the organization. - Without either value, create a new conversation.
If the matched conversation is archived, a new conversation is created. Always retain the returned conversation_id: it is the most precise way to continue a thread. external_user_id is useful when your system prefers to retain only its own contact identifier.
phone and email take no part in this resolution: the conversation is always identified by conversation_id or external_user_id. Those two fields feed the contact record instead.
Contact phone and email
Section titled “Contact phone and email”Map your own lead’s phone number and email onto these two fields. They are optional, and a caller that sends neither keeps working exactly as before.
SquadOS attaches each value to the record of the person identified by external_user_id, with three guarantees:
- A value already on file is never replaced. Changing a contact’s phone number changes where their messages go, and that does not happen as a side effect of a request.
- A value that already belongs to another contact is not saved, and the two people are not merged. The collision is recorded for review in the panel.
- None of this rejects the message. A malformed value is ignored, a collision is only recorded, and the conversation proceeds in all three cases.
Send the fields on every request. Repeating the same value does nothing, and that is how the phone number reaches the records of people who were already talking to you before you mapped the field.
curl -X POST https://api.squados.io/v1/chat/API_INBOX_ID \ -H "Authorization: Bearer pk_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "message": "I want to talk about my subscription", "external_user_id": "crm-customer-123", "user_name": "Maria Silva", "phone": "+55 11 99999-8888", "email": "[email protected]" }'curl -X POST https://api.squados.io/v1/chat/API_INBOX_ID \ -H "Authorization: Bearer pk_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "message": "I am back. What is my order status?", "sync": true, "external_user_id": "crm-customer-123", "user_name": "Maria Silva" }'Direct response
Section titled “Direct response”The response is direct when there is no webhook_url or when sync: true. The endpoint contract has no fixed 10-second timeout; configure your client timeout to accommodate the agent’s execution time.
Response 200
{ "success": true, "conversation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "message_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "response": "Our store is open Monday through Friday from 9 AM to 6 PM.", "model": "provider/model", "credits_used": 1, "attachments_processed": 0}model and credits_used reflect the actual execution and vary by agent, provider, and usage.
Asynchronous webhook response
Section titled “Asynchronous webhook response”Send webhook_url and do not use sync: true. The API acknowledges receipt with 202; this initial acknowledgment does not contain the assistant response or its message_id.
curl -X POST https://api.squados.io/v1/chat/API_INBOX_ID \ -H "Authorization: Bearer pk_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "message": "Analyze the attached report", "sync": false, "webhook_url": "https://integration.example.com/squados/callback", "metadata": { "ticket_id": "TKT-12345" } }'With sync: false, processing goes through the asynchronous queue and the acknowledgment includes its group:
{ "status": "debounced", "group_id": "d4e5f6a7-b8c9-0123-def0-234567890123", "conversation_id": "c3d4e5f6-a7b8-9012-cdef-123456789012"}If sync is omitted, the asynchronous acknowledgment can use "status": "processing" and contain only status and conversation_id.
When processing finishes, SquadOS sends a POST to the supplied URL. A successful callback uses event: "message.completed" and includes success, agent_id, conversation_id, message_id, response, model, credits_used, attachments_processed, responding_agent_name, metadata, and timestamp. An agent transfer can produce an earlier callback with pre_transfer: true. See Webhooks for payloads and delivery considerations.
Store a message without running the agent
Section titled “Store a message without running the agent”Use role: "assistant" to record a message produced outside SquadOS, or onlyStorage: true to persist either role without running the pipeline. Provide conversation_id when the message must enter an existing conversation; without it, normal resolution rules apply and a conversation can be created.
curl -X POST https://api.squados.io/v1/chat/API_INBOX_ID \ -H "Authorization: Bearer pk_your_key_here" \ -H "Content-Type: application/json" \ -d '{ "message": "Your order has been processed.", "role": "assistant", "conversation_id": "550e8400-e29b-41d4-a716-446655440000" }'Response 200
{ "success": true, "conversation_id": "550e8400-e29b-41d4-a716-446655440000", "message_id": null}Send an attachment without text
Section titled “Send an attachment without text”message can be omitted when attachments contains at least one item:
{ "sync": true, "attachments": [ { "name": "contract.pdf", "url": "https://files.example.com/contract.pdf", "type": "file", "mimeType": "application/pdf" } ]}Common errors
Section titled “Common errors”| HTTP | code | When it happens |
|---|---|---|
400 | invalid_request | Invalid JSON, invalid role, wrong field type, or both text and attachments are missing. |
401 | unauthorized | Token is missing, invalid, revoked, or outside its validity period. |
403 | forbidden | The inbox does not belong to the token’s organization or is not an API inbox. |
403 | trigger_inactive | The API inbox is inactive or the legacy agent does not have an active API inbox. |
404 | not_found | Route or conversation_id was not found in the organization. |
Error responses use the shape { "error": "message", "code": "code" }. See Errors for the shared API contract.