Chat API
Chat threads represent conversations between users, while messages are individual communications within those threads.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /chat/threads | List chat threads |
| POST | /chat/threads | Create a new chat thread |
| GET | /chat/threads/{thread_id} | Get a specific chat thread |
| POST | /chat/threads/{thread_id}/messages | Add a message to a chat thread |
| PATCH | /chat/threads/{thread_id}/messages/{message_id} | Update a message in a chat thread |
| GET | /chat/threads/{thread_id}/participants | Get participants of a chat thread |
| POST | /chat/threads/{thread_id}/participants | Add a participant to a chat thread |
Thread Endpoints
List Threads
GET /api/v2/chat/threads
Retrieves a paginated list of chat threads. All filters are optional. When client_account_id is omitted, threads are returned across all of the user’s eligible client accounts.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| client_account_id | integer | No | Filter threads by client account ID |
| provider_client_account_id | integer | No | Filter threads by provider client account ID |
| relation_type | string | No | Filter threads by relation type (e.g. document, businesspartner) |
| relation_id | integer | No | Filter threads by relation ID |
| free_text | string | No | Full-text search across thread subjects and message content (case-insensitive, partial word match) |
| order_by | string | No | Sort field (e.g. created_at, -last_message_created_at) |
| page | integer | No | Page number for paginated results (default: 1) |
| per_page | integer | No | Number of items per page (default: 100) |
Search Examples
Search threads by text content:
GET /api/v2/chat/threads?free_text=invoice
Combine search with other filters:
GET /api/v2/chat/threads?free_text=invoice&client_account_id=123&relation_type=document
Create Thread
POST /api/v2/chat/threads
Creates a new chat thread. The authenticated user is recorded as created_by_id. The user must have access to either the client_account_id or the provider_client_account_id of the thread being created. Messages can be included in the request body to be created together with the thread.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| client_account_id | integer | Yes | The client account the thread belongs to |
| provider_client_account_id | integer | No | The provider client account (set when the thread spans an accountant–client relationship) |
| subject | string | No | The subject line of the thread |
| relation_type | string | No | The type of entity the thread is associated with (e.g. document, businesspartner) |
| relation_id | integer | No | The ID of the related entity |
| is_completed | boolean | No | Whether the thread is marked as completed (default: false) |
| is_archived | boolean | No | Whether the thread is archived (default: false) |
| messages | array | No | Initial messages to add to the thread. See Add Message for the message body shape |
Example Request
{
"client_account_id": 123,
"provider_client_account_id": 7,
"subject": "Invoice 1004 - missing VAT code",
"relation_type": "document",
"relation_id": 5678,
"messages": [
{
"message": "Could you confirm which VAT code applies to this purchase?"
}
]
}
Response
Returns the created thread with status 201 Created.
{
"id": 42,
"created_by_id": 12,
"created_at": "2026-06-01T09:00:00Z",
"last_message_created_at": "2026-06-01T09:00:00Z",
"client_account_id": 123,
"provider_client_account_id": 7,
"subject": "Invoice 1004 - missing VAT code",
"relation_type": "document",
"relation_id": 5678,
"is_completed": false,
"is_archived": false
}
Get Thread
GET /api/v2/chat/threads/{thread_id}
Retrieves a specific chat thread by ID.
Response
Returns the thread object. See Thread Attributes.
Errors
| Status | Description |
|---|---|
| 403 | No access to the thread’s client account or provider client account |
| 404 | Thread not found |
Message Endpoints
Add Message
POST /api/v2/chat/threads/{thread_id}/messages
Adds a new message to a chat thread. The authenticated user is recorded as both from_user_id and created_by_id. The user must have access to either the thread’s client_account_id or provider_client_account_id.
Messages containing a codeaction block trigger automatic processing — the block is first run in preview mode and, if no approval is required, automatically re-run in write mode. See Code Blocks in Messages. Assistant replies that use the newer provider-neutral tool-calling format instead carry a tool_mode of PREVIEW or WRITE — see Tool-Calling Messages for how to approve those.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| message | string | Yes, unless approves_message_id is set |
The content of the message. May contain codeaction, approvecodeaction, or coderesult blocks |
| is_draft | boolean | No | Whether the message is a draft (default: false) |
| approves_message_id | integer | No | Approves a PREVIEW tool-calling message in this thread, triggering write-mode re-execution of its calls that need approval. message may be omitted (or empty) when this is set. See Tool-Calling Messages |
| attachment_ids | array of integers | No | Ids of files uploaded with type=CHAT_ATTACHMENT (images and PDF only, see Files) to send with the message. message may be empty when this is set. Each file must have been uploaded by the sender for the thread’s client account and not already be attached to a message |
Example Request
{
"message": "Posted as 6300 (Office Supplies). Let me know if that needs adjusting."
}
With a file:
{
"message": "What is this receipt for?",
"attachment_ids": [8811]
}
Response
Returns the created message with status 201 Created. Code blocks in the message field are obscured in the response (see Code Blocks in Messages).
{
"id": 1042,
"created_by_id": 12,
"created_at": "2026-06-01T09:15:00Z",
"thread_id": 42,
"from_user_id": 12,
"message": "Posted as 6300 (Office Supplies). Let me know if that needs adjusting.",
"is_draft": false,
"action_items": [],
"action_mode": null,
"action_error": false,
"tool_calls": null,
"tool_results": null,
"previews": null,
"tool_mode": null,
"approves_message_id": null,
"attachments": []
}
attachments lists the files sent with the message, in the shape of the Files resource (including thumbnail_url, preview_url and download_url). Chat attachments are read by the assistant but never enter the document pipeline; a user who wants such a file booked uploads it as a document instead.
Errors
| Status | Description |
|---|---|
| 400 | message, approves_message_id and attachment_ids all missing, attachment_ids is not a list of integers, approves_message_id does not reference a PREVIEW message in this thread, or that preview has already been approved |
| 403 | No access to the thread’s client account or provider client account, or an attachment_ids entry is not an unattached CHAT_ATTACHMENT file the sender uploaded for that client account |
| 404 | Thread not found |
Update Message
PATCH /api/v2/chat/threads/{thread_id}/messages/{message_id}
Updates an existing message in a chat thread. Useful for editing draft messages before they are finalized, or for replacing message content. The authenticated user is recorded as the new from_user_id.
Only the fields below are accepted in the update payload — thread_id, from_user_id, created_at, and created_by_id cannot be changed.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| message | string | No | New content for the message |
| is_draft | boolean | No | Update the draft flag |
Example Request
Promote a draft to a sent message:
{
"message": "Confirmed — VAT code 3 (25%) is correct for this purchase.",
"is_draft": false
}
Response
Returns the updated message with status 201 Created. As with create, code blocks in the message field are obscured.
Errors
| Status | Description |
|---|---|
| 403 | No access to the thread’s client account or provider client account |
| 404 | Thread or message not found |
Participant Endpoints
List Participants
GET /api/v2/chat/threads/{thread_id}/participants
Returns the participants of a chat thread.
Response
[
{
"id": 11,
"created_by_id": 12,
"created_at": "2026-06-01T09:00:00Z",
"user_id": 12,
"thread_id": 42
},
{
"id": 12,
"created_by_id": 12,
"created_at": "2026-06-01T09:01:00Z",
"user_id": 34,
"thread_id": 42
}
]
Errors
| Status | Description |
|---|---|
| 403 | No access to the thread’s client account or provider client account |
| 404 | Thread not found |
Add Participant
POST /api/v2/chat/threads/{thread_id}/participants
Adds a participant (user) to a chat thread. The authenticated user is recorded as created_by_id.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| user_id | integer | Yes | The ID of the user to add as a participant |
Example Request
{
"user_id": 34
}
Response
Returns the created participant with status 201 Created.
{
"id": 12,
"created_by_id": 12,
"created_at": "2026-06-01T09:01:00Z",
"user_id": 34,
"thread_id": 42
}
Errors
| Status | Description |
|---|---|
| 403 | No access to the thread’s client account or provider client account |
| 404 | Thread not found |
Thread Attributes
| Attribute | Type | Description |
|---|---|---|
| id | integer | The ID of the chat thread |
| created_by_id | integer | The ID of the user who created the thread |
| created_at | string | The date and time when the thread was created (ISO 8601) |
| last_message_created_at | string | The date and time of the most recent message in the thread (ISO 8601, read-only) |
| client_account_id | integer | The client account ID associated with the thread |
| provider_client_account_id | integer | The provider client account ID associated with the thread |
| subject | string | The subject line of the thread |
| relation_type | string | The type of entity the thread is associated with (e.g. document, businesspartner) |
| relation_id | integer | The ID of the related entity |
| is_completed | boolean | Whether the thread is marked as completed |
| is_archived | boolean | Whether the thread is archived |
Message Attributes
| Attribute | Type | Description |
|---|---|---|
| id | integer | The ID of the message |
| thread_id | integer | The ID of the chat thread associated with the message |
| from_user_id | integer | The ID of the user who sent the message |
| message | string | The content of the message. Code blocks are obscured (see Code Blocks in Messages) |
| is_draft | boolean | Whether the message is a draft |
| created_at | string | The date and time when the message was created (ISO 8601) |
| created_by_id | integer | The ID of the user who created the message |
| action_items | array | List of preview objects extracted from coderesult blocks (legacy codeaction format) |
| action_mode | string | The execution mode from coderesult blocks (preview or write), or null (legacy codeaction format) |
| action_error | boolean | true if the coderesult block reported an execution error (legacy codeaction format) |
| tool_calls | array | Tools the assistant called to produce this message, or null. Read-only — see Tool-Calling Messages |
| tool_results | array | Results returned by each entry in tool_calls, or null. Read-only |
| previews | array | What each write-tool call would do (or did); each entry’s need_approval gates the approve action. Read-only |
| tool_mode | string | PREVIEW, WRITE, or null for a legacy codeaction row. Read-only |
| approves_message_id | integer | Set on an approval row to the PREVIEW message it approves, or null |
Participant Attributes
| Attribute | Type | Description |
|---|---|---|
| id | integer | The ID of the participant |
| thread_id | integer | The ID of the chat thread associated with the participant |
| user_id | integer | The ID of the user associated with the participant |
| created_by_id | integer | The ID of the user who added this participant |
| created_at | string | The date and time when the participant was added (ISO 8601) |
Code Blocks in Messages
Messages can contain special code blocks that are processed by the system:
| Block Type | Description |
|---|---|
| codeaction | Executable code blocks that perform actions. Initially run in preview mode. If no approval is needed, automatically run in write mode. |
| approvecodeaction | Approval blocks that trigger write-mode execution of the previous codeaction block. The result is posted as a new message. |
| coderesult | Result blocks containing execution output and preview objects. Generated automatically after processing codeaction blocks. |
Code Block Processing
- codeaction blocks:
- First run in preview mode
- If no errors and no objects need approval, automatically run in write mode
- A coderesult block is added after the codeaction block
- Results are posted as a new message
- approvecodeaction blocks:
- Finds the last codeaction block in the thread
- Runs that block in write mode (is_approved=true)
- Posts the result as a new message containing only the coderesult block
API Response Format
When retrieving messages, code blocks are handled securely:
- Code content in codeaction and coderesult blocks is hidden with “[code hidden]”
- Preview objects from coderesult blocks are extracted into structured data
- The execution mode is included when available
Example response for a message with processed code blocks:
{
"id": 123,
"message": "Let's process this:\n```codeaction\n[code hidden]\n```\n```coderesult\n[code hidden]\n```",
"action_items": [
{
"type": "preview",
"needs_approval": true,
"id": 1
}
],
"action_mode": "preview"
}
Example of an approval message:
{
"id": 124,
"message": "```approvecodeaction\n```",
"action_items": [],
"action_mode": null
}
Example of the result message after approval:
{
"id": 125,
"message": "```coderesult\n[code hidden]\n```",
"action_items": [
{
"type": "preview",
"needs_approval": false,
"id": 1
}
],
"action_mode": "write"
}
Tool-Calling Messages
This is the current assistant-message format, alongside the legacy codeaction format above. A tool_mode of null means the row predates tool-calling and uses codeaction blocks instead; PREVIEW and WRITE rows carry structured tool_calls, tool_results, and previews instead of embedding them in message (which holds prose only).
- The assistant replies with a
PREVIEWmessage.tool_callslists every tool it called;tool_resultsholds each call’s result;previewsdescribes what any write action would do, withneed_approval: trueon entries that require a human decision. - If no preview entry needs approval, the assistant automatically re-runs the write-capable calls and posts a
WRITEmessage with the same shape,previews[].need_approvalnowfalse. - If approval is required, the user approves by posting
POST /chat/threads/{thread_id}/messageswithapproves_message_idset to thePREVIEWmessage’sid(and nomessage, or an empty one). This re-executes only the calls that needed approval, in write mode, and records a newWRITEmessage from the assistant. - A
PREVIEWmessage can only be approved once — approving it again returns a 400 error.
Example PREVIEW message awaiting approval:
{
"id": 1050,
"from_user_id": 2,
"message": "I can book this as office supplies — want me to go ahead?",
"tool_mode": "PREVIEW",
"tool_calls": [
{
"id": "call_write",
"name": "create_journal_entry",
"arguments": { "data": { "client_account_id": 123 } },
"needs_approval": true
}
],
"tool_results": [
{ "tool_call_id": "call_write", "content": "{}", "is_error": false }
],
"previews": [
{ "type": "JournalEntry", "data": {}, "need_approval": true }
],
"approves_message_id": null
}
Approving it:
POST /api/v2/chat/threads/42/messages
{
"approves_message_id": 1050
}
Resulting WRITE message from the assistant:
{
"id": 1052,
"from_user_id": 2,
"message": "Booked as 6300 (Office Supplies).",
"tool_mode": "WRITE",
"tool_calls": [
{
"id": "call_write",
"name": "create_journal_entry",
"arguments": { "data": { "client_account_id": 123 } },
"needs_approval": true
}
],
"tool_results": [
{ "tool_call_id": "call_write", "content": "{\"id\": 55}", "is_error": false }
],
"previews": [
{ "type": "JournalEntry", "data": { "id": 55 }, "need_approval": false }
],
"approves_message_id": null
}
Relationships
| Relationship | Type | Description |
|---|---|---|
| messages | [Message] | The messages in a chat thread |
| participants | [Participant] | The participants in a chat thread |
Notes
- All endpoints require authentication.
- All filter parameters on
GET /chat/threadsare optional. Usefree_textonly when you need full-text search across thread subjects and messages. - Access to threads and messages is restricted based on the user’s eligible client accounts. Access is granted if the user can access either the thread’s
client_account_idor itsprovider_client_account_id. - Error responses:
- 403 Forbidden: When trying to access a thread the user is not associated with.
- 404 Not Found: When a specified thread or message doesn’t exist.