Repository navigation
API Chat
Canonical source:
docs/12-reference/api-reference-chat.md— this page is rendered from it on every publish; edit the source.
Base URL: http://localhost:4000/api/v1 (via nginx) or http://localhost:4002/api/v1 (direct)
Create a new chat thread.
Auth: Bearer token Request Body:
{
"title": "My Chat",
"routingMode": "AUTO",
"preferredProvider": "anthropic",
"preferredModel": "claude-sonnet-4",
"systemPrompt": "You are a helpful assistant.",
"temperature": 0.7,
"maxTokens": 4096,
"contextPackIds": ["clxyz..."]
}All fields are optional.
Response 201:
{
"id": "clxyz...",
"userId": "clxyz...",
"title": "My Chat",
"routingMode": "AUTO",
"lastProvider": null,
"lastModel": null,
"isPinned": false,
"isArchived": false,
"preferredProvider": "anthropic",
"preferredModel": "claude-sonnet-4",
"contextPackIds": ["clxyz..."],
"systemPrompt": "You are a helpful assistant.",
"temperature": 0.7,
"maxTokens": 4096,
"createdAt": "2026-04-11T10:00:00.000Z",
"updatedAt": "2026-04-11T10:00:00.000Z"
}curl:
curl -X POST http://localhost:4000/api/v1/chat-threads \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"My Chat","routingMode":"AUTO"}'List the current user's threads with message counts.
Auth: Bearer token Query Parameters:
-
page(int, default: 1) -
limit(int, default: 20, max: 100) -
search(string) — search by title
Response 200:
{
"data": [
{
"id": "clxyz...",
"title": "My Chat",
"routingMode": "AUTO",
"lastProvider": "anthropic",
"lastModel": "claude-sonnet-4",
"isPinned": false,
"isArchived": false,
"createdAt": "2026-04-11T10:00:00.000Z",
"_count": { "messages": 12 }
}
],
"meta": { "page": 1, "limit": 20, "total": 3, "totalPages": 1 }
}Get a specific thread.
Auth: Bearer token (must own thread)
Response 200: ChatThread object
Errors: 404 ENTITY_NOT_FOUND, 403 FORBIDDEN
Copy the thread, up to and including one message, into a new thread (a branch). The source is untouched. Counts against the daily chat limit.
Auth: Bearer token (must own thread)
Request Body: { "fromMessageId": "<message id in this thread>", "cut": "INCLUDE" }
— cut is optional: INCLUDE (default) keeps the pivot message, BEFORE
stops just short of it (ADR-132, used by "Edit in a new branch").
Response 201: the new ChatThread, with branchedFromThreadId,
branchedFromMessageId and branchRootThreadId set (ADR-130)
Errors: 404 ENTITY_NOT_FOUND (thread or message not found, or message from
another thread), 403 FORBIDDEN, 429 PLAN_DAILY_CHAT_LIMIT_EXCEEDED
Where the thread sits in its branch family.
Auth: Bearer token (must own thread; every lineage read is scoped to the caller) Response 200:
{
"threadId": "t-branch",
"parent": {
"id": "t-src",
"title": "Trip plan",
"createdAt": "…",
"branchedFromMessageId": null
},
"parentDeleted": false,
"forkMessageId": "m-7",
"branches": [
{ "id": "t-b2", "title": "Trip plan", "createdAt": "…", "branchedFromMessageId": "m-3" }
]
}parent is null for a root thread and for a branch whose source was deleted —
parentDeleted tells them apart. At most 50 direct branches are listed.
Errors: 404 ENTITY_NOT_FOUND, 403 FORBIDDEN
Update a thread.
Auth: Bearer token (must own thread) Request Body: Any combination of:
{
"title": "New Title",
"routingMode": "LOCAL_ONLY",
"preferredProvider": null,
"preferredModel": null,
"systemPrompt": "Updated prompt",
"temperature": 0.5,
"maxTokens": 8192,
"contextPackIds": [],
"isPinned": true,
"isArchived": false
}Response 200: Updated ChatThread
Delete a thread and all its messages.
Auth: Bearer token (must own thread) Response 200: Deleted ChatThread
Send a user message. Triggers routing and AI response.
Auth: Bearer token Request Body:
{
"threadId": "clxyz...",
"content": "Hello, how are you?",
"fileIds": ["clfile1...", "clfile2..."],
"quotes": [{ "sourceMessageId": "clmsg-earlier...", "text": "Day 2: Louvre" }]
}quotes (optional, ADR-131): up to 3 selections, text 1-2,000 characters, each
from a USER or ASSISTANT message of this thread. A quote alone (empty
content) is a valid turn. Stored on the new message's metadata.quotes with
the source role. Errors: 404 QUOTE_SOURCE_NOT_FOUND when a source is not
in this thread (deleted, rewound, or another conversation's).
Response 201: The created USER message
{
"id": "clmsg...",
"threadId": "clxyz...",
"role": "USER",
"content": "Hello, how are you?",
"provider": null,
"model": null,
"createdAt": "2026-04-11T10:00:00.000Z"
}Side effects: Publishes message.created event which triggers:
- Routing decision
- AI provider call
- ASSISTANT message creation
- SSE event emission
List messages in a thread (cursor-paginated, newest first). Cursor rather than offset: an offset window shifts whenever a message is appended between two requests, duplicating or dropping rows in a client that merges pages — a cursor anchored to a specific message's id has no such window.
Auth: Bearer token (must own thread) Query Parameters:
-
before(string, optional) — a message id already seen; fetches the page older than it. Omitted, returns the newest messages. -
limit(int, default: 50, max: 100)
Response 200:
{
"data": [
{
"id": "clmsg1...",
"role": "ASSISTANT",
"content": "I'm doing well! How can I help?",
"provider": "anthropic",
"model": "claude-sonnet-4",
"routingMode": "AUTO",
"routerModel": "gemma3:4b",
"usedFallback": false,
"inputTokens": 150,
"outputTokens": 45,
"estimatedCost": "0.00023000",
"latencyMs": 1200,
"feedback": null,
"metadata": null,
"createdAt": "2026-04-11T10:00:01.000Z"
},
{
"id": "clmsg0...",
"role": "USER",
"content": "Hello, how are you?",
"provider": null,
"model": null,
"createdAt": "2026-04-11T10:00:00.000Z"
}
],
"meta": { "limit": 20, "total": 2, "nextBefore": null }
}nextBefore is the id to pass back as before for the next older page;
null means nothing older is left. A full page (data.length === limit)
still might be the last one — the only way to know for certain is that the
next fetch with that cursor comes back empty.
Get a specific message.
Auth: Bearer token (must own thread) Response 200: ChatMessage object
Regenerate an AI response for a message.
Auth: Bearer token (must own thread) Request Body (optional, ADR-132):
{ "routingMode": "MANUAL_MODEL", "provider": "ANTHROPIC", "model": "claude-opus-5" }routingMode is AUTO (re-route from scratch) or MANUAL_MODEL (needs both
provider and model). Omitted: the thread's pinned model, else the mode the
turn was first routed with (no body at all is the same as {}). The same plan
check as a new message runs first.
Response 201: the message it was called on; the new answer arrives over the stream
Errors: 403/429 plan and quota refusals (as for POST /chat-messages)
Answer the "which pack?" card of an AI chat save (ADR-134).
Auth: Bearer token (must own the thread)
Request Body: { "packId": "<one of the offered packs>" } or { "newPack": true }
Response 200: the updated contextSave record — { status: "SAVED", pack: { id, name, created, link }, memory? }
Errors: 404 (no such message, or not yours), 409 CONTEXT_SAVE_NOT_PENDING
(already saved, or a second click lost the claim), 400 CONTEXT_SAVE_UNKNOWN_PACK,
403 PLAN_FEATURE_DISABLED, 429 PLAN_CONTEXT_PACK_LIMIT_EXCEEDED,
503 CONTEXT_SAVE_UNAVAILABLE — on a failure the card returns to "choose".
Set feedback on a message (thumbs up/down).
Auth: Bearer token (must own thread) Request Body:
{ "feedback": "positive" }Response 200: Updated ChatMessage
Server-Sent Events stream for real-time message updates.
Auth: Bearer token Response: SSE event stream
Events emitted:
data: {"threadId":"clxyz...","type":"completion","message":{...}}
data: {"threadId":"clxyz...","type":"error","error":"All providers failed"}
Important: Do NOT use EventSource API (cannot set Authorization header). Use fetch() with ReadableStream:
Send a single prompt to 2-5 models simultaneously. All models receive the same assembled context. Results are returned together once all models have responded (or failed).
Auth: Bearer token (must own thread) Request Body:
{
"threadId": "clxyz...",
"content": "Explain the difference between REST and GraphQL",
"models": [
{ "provider": "anthropic", "model": "claude-sonnet-4" },
{ "provider": "openai", "model": "gpt-4o-mini" },
{ "provider": "gemini", "model": "gemini-2.5-flash" }
],
"fileIds": []
}| Field | Type | Required | Description |
|---|---|---|---|
threadId |
string | Yes | Thread to attach messages to |
content |
string | Yes | User prompt (max 10,000 chars) |
models |
array of {provider, model} | Yes | 2-5 provider/model pairs |
fileIds |
string[] | No | Optional file attachments |
Response 200:
{
"threadId": "clxyz...",
"userMessageId": "clmsg0...",
"results": [
{
"provider": "anthropic",
"model": "claude-sonnet-4",
"status": "fulfilled",
"content": "REST is an architectural style...",
"messageId": "clmsg1...",
"inputTokens": 280,
"outputTokens": 350,
"latencyMs": 1450
},
{
"provider": "openai",
"model": "gpt-4o-mini",
"status": "fulfilled",
"content": "The key differences between REST and GraphQL...",
"messageId": "clmsg2...",
"inputTokens": 280,
"outputTokens": 290,
"latencyMs": 980
},
{
"provider": "gemini",
"model": "gemini-2.5-flash",
"status": "rejected",
"content": null,
"messageId": null,
"error": "Provider timeout after 30000ms",
"latencyMs": 30000
}
],
"totalLatencyMs": 30000
}Errors:
-
400 VALIDATION_ERROR-- fewer than 2 or more than 5 models, empty content -
403 FORBIDDEN-- user does not own the thread -
404 ENTITY_NOT_FOUND-- thread not found
curl:
curl -X POST http://localhost:4000/api/v1/chat-messages/parallel \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"threadId": "clxyz...",
"content": "Explain REST vs GraphQL",
"models": [
{"provider": "anthropic", "model": "claude-sonnet-4"},
{"provider": "openai", "model": "gpt-4o-mini"}
]
}'const response = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
const reader = response.body.getReader();curl:
curl -N http://localhost:4000/api/v1/chat-messages/stream/clxyz... \
-H "Authorization: Bearer $TOKEN"ClawAI Wiki · source-controlled under wiki/ · generated/mirrored from the current repository knowledge layer.
ClawAI Wiki
Product
- Product-Vision
- Flagship-Features
- Business-Overview
- Requirements-Register
- Drift-Log
- Runtime-Managed-Connector-Providers
- Threads
- ADR-Index
Architecture
- System-at-a-Glance
- Architecture-Map
- System-Architecture
- Message-Flow
- End-to-End-Data-Flow
- Routing-Engine
- Event-Bus
- Security-Architecture
Platform
- Backend-Services
- Frontend-Architecture
- Shared-Packages
- Database-Reference
- API-Reference
- Provider-Catalog
- Memory-and-Context-Architecture
- File-Support-Architecture
- Research-and-Web-Crawling
Runtime & Ops
- Docker-Guide
- Nginx-Reference
- CI-CD-Pipeline
- Logging-and-Observability
- Operational-Runbooks
- Troubleshooting
Quality
Engineering OS
- AI-Native-Engineering-OS
- Generated-Manifests
- Rules-Catalog
- Skills-Catalog
- Context-Layer
- Engineering-Memory
- Tooling-and-Automation
- Scripts-and-Operations
- ESLint-and-Architecture-Enforcement
- Extended-Work-Skills
Agents
Reference