Chat API
The chat API is the same endpoint the hosted UI uses to capture prompts, stream model responses, and emit DSL deltas. It is a Server‑Sent Events (SSE) interface: the request body is JSON, the response body is a stream of data: records that you parse incrementally.
Authentication
weldr login before issuing API calls.X-Test-User: . This header is ignored in production.POST /api/chat
Send a user message and receive a streaming response that mixes text deltas, DSL updates, and capability traces.
Request Schema
interface ChatRequest {
id: string; // chatId (uuid). Supply your own via crypto.randomUUID().
message: {
id: string; // messageId (uuid)
role: 'user';
parts: Array<
| { type: 'text'; text: string } // plain text (primary path)
| { type: 'file'; mediaType: 'image/jpeg' | 'image/png'; name: string; url: string }
>;
};
selectedChatModel: 'chat-model' | 'chat-model-reasoning';
selectedVisibilityType: 'public' | 'private';
}All fields are required by app/(chat)/api/chat/schema.ts. Omit optional behaviors like temperature or DSL toggles—the server chooses sane defaults (DSL extraction is always on unless ENABLE_DSL_EXTRACTION=false).
Example Request
curl -N -X POST http://localhost:3000/api/chat \
-H "Content-Type: application/json" \
-H "Cookie: sb-access-token=...; sb-refresh-token=..." \
-d '{
"id": "ad53cec3-7819-46e2-92f7-0b03036ad6ce",
"message": {
"id": "b52be0c7-e0c9-45a8-aba3-3e6d530467c1",
"role": "user",
"parts": [
{ "type": "text", "text": "Build a helpdesk with escalations and SLAs" }
]
},
"selectedChatModel": "chat-model",
"selectedVisibilityType": "private"
}'SSE Event Types
| Event type | Payload | Description |
| ------------ | ------- | ----------- |
| text-start | { id } | Assistant placeholder message created in the database. |
| text-delta | { id, delta } | Text tokens streamed from the orchestrator. Append delta strings in order to reconstruct the full response. |
| message-metadata | { messageMetadata: { dsl_update \| phase_change \| dsl_sync_error } } | DSL deltas arrive inside dsl_update.delta (JSON Patch ops) with reasoning. Phase changes surface as phase_change.phase. Failures emit dsl_sync_error. |
| data-uiIntentTrace | { trace } | Debug trace describing UI intents extracted from the conversation. |
| data-capability-trace | { trace } | Similar telemetry for capability planners (notifications, automations, etc.). |
| data-tool_call | { tool } | Low-level tool usage emitted by the orchestrator. |
| text-end | { id } | Signals that textual streaming finished. |
| finish | {} | Final envelope; close your SSE reader when this type arrives. |
| error | { message } | Non-recoverable failure. The stream closes immediately afterwards. |
Each line follows the SSE format data: {...}\n\n. When you receive a message-metadata.dsl_update, persist the delta or inspect messageMetadata.dsl_update.reasoning to understand why the DSL changed.
Streaming Example (TypeScript)
async function streamChat(body: ChatRequest) {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (!response.body) throw new Error('No stream returned');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
for (const line of chunk.split('\n')) {
if (!line.startsWith('data: ')) continue;
const event = JSON.parse(line.slice(6));
switch (event.type) {
case 'text-delta':
appendText(event.delta);
break;
case 'message-metadata':
if (event.messageMetadata.dsl_update) {
hydrateDsl(event.messageMetadata.dsl_update.delta);
}
break;
case 'finish':
console.log('done');
break;
}
}
}
}Error Responses
The handler throws typed ChatSDKErrors which turn into JSON payloads:
{
"error": {
"code": "rate_limit:chat",
"message": "You have reached your daily limit of 50 messages. Please try again tomorrow or upgrade your account."
}
}Rate Limits
enforceBurstRateLimit).MAX_MESSAGES_* env vars (defaults: guests = 10, builders = 50, pro = 500). Limit enforcement lives in lib/rate-limit.ts.message-metadata.dsl_sync_error.GET /api/chat/:id/stream
Resume an interrupted stream. The resumable-stream layer stores the latest streamId for each chat; requesting /api/chat/ will:
Example:
curl -N http://localhost:3000/api/chat/ad53cec3-7819-46e2-92f7-0b03036ad6ce/stream \
-H "Cookie: sb-access-token=...; sb-refresh-token=..."Development & Testing Tips
app_model_* tables.ENABLE_TEST_MODE=true and send X-Test-User: builder@test.dev to bypass standard rate limits.ENABLE_DSL_EXTRACTION=false if you need to compare streaming behavior without schema mutations (rare).