messages table, the context-window loader, and the
“how do I switch models mid-conversation” question — every endpoint that
follows uses the same Anthropic shape regardless of underlying model.
POST /v1/threads — Create
Body
Response (201)
POST /v1/threads/:id/messages — Send a turn
The meat. Customer sends just the new user content; qlaud loads thread history, runs the upstream call, persists both turns, returns the assistant response.Body
Standard Anthropic Messages fields PLUS:Response
Standard Anthropic Messages response with two extras attached:stream: true, the response is text/event-stream instead and the
thread/seq attribution lands in headers:
Cross-shape works: pass
model: "gpt-5.4" to a thread of Claude turns and
qlaud translates transparently. The conversation history persists; the
underlying model can change per request.Streaming
stream: true returns an Anthropic-shape SSE stream of event: /
data: pairs. Standard Anthropic events flow through verbatim:
message_start,message_delta,message_stopcontent_block_start,content_block_delta,content_block_stopping
Example stream for a “create a Linear ticket” turn (truncated):
event: starting with qlaud. as a
side-channel UI signal (render a spinner, show “calling Linear…”,
update a progress bar). All standard Anthropic events feed your
existing message-renderer untouched.
When stream: false (default), qlaud runs the same dispatch loop
internally and only returns the final assistant message — same
shape as Anthropic’s non-streaming Messages response, with the
extra thread_id / seq / cost_micros fields attached.
All tool kinds work the same way in the stream
Whether the model calls a catalog MCP (Linear), a first-party builtin (web_search, code_execution, send_email), or a
custom webhook you registered via
POST /v1/tools, the dispatch flow is
identical from the client’s perspective. Same qlaud.tool_dispatch_*
events, same multiplexing, same single SSE connection. Your custom
webhook gets POSTed by qlaud just like any other tool, the result is
streamed back into the same SSE, and the model continues.
For your custom tools to be auto-discoverable mid-conversation, send
the request with no tools array — tools_mode defaults to
"dynamic" and qlaud injects the 4 meta-tools. The model then calls
qlaud_search_tools(intent: "...") and every tool you’ve registered
appears in the results alongside catalog connectors (search queries
the full tools table for your account — no kind filter).
If you pin tools_mode: "explicit" and pass a specific tools: [...]
array, only those tool IDs are visible. Use this when you want to
restrict the surface (e.g. a dedicated “image-only” turn).
Streaming + tools — supported models
stream: true with the tool-dispatch loop runs through a unified
SSE bridge: Anthropic-shape upstreams pass through verbatim, every
OpenAI-shape upstream is translated to Anthropic-shape events on the
fly. Your client always sees the same content_block_delta /
tool_use event vocabulary regardless of which model you picked.
The
qlaud.tool_dispatch_start / qlaud.tool_dispatch_done /
qlaud.iteration_start events fire identically across all model
families. Same client code, no per-provider branches.
GET /v1/threads — List
Query
Response
GET /v1/threads/:id — Get one
Returns the same shape as a list entry. 404 if you don’t own the thread or it’s been soft-deleted.GET /v1/threads/:id/messages — List turns
Cursor-paginated. Supports both directions so chat UIs (latest-first, scroll up for older) and log replay UIs (oldest-first, scroll down) both fit cleanly.Query
Pagination patterns:
- Chat UI (newest first, scroll up for older):
- Log replay (oldest first, scroll down for newer):
null.
Response
DELETE /v1/threads/:id — Soft delete
Errors
Limits (v1)
- History capped at last 50 turns when loading for the upstream call. Token-aware truncation comes later.
- Streaming +
toolscombo not yet supported. Use one or the other. - Each turn embeds asynchronously into /v1/search — search becomes available within a few seconds of the turn persisting.