REST API Chat Integration
Use the schema chat endpoint to add a SyntheticBrew agent experience to a product or service. Streaming responses use Server-Sent Events (SSE); clients that only need a completed answer can request one JSON response.
Before you start
Section titled “Before you start”You need:
- a schema with Chat Enabled and an entry agent;
- an API key with the
chatscope, or a valid end-user JWT; - the schema’s immutable name, such as
support-handbook; - the origin of your Cloud workspace or Enterprise deployment.
Set the origin once in examples:
export SYNTHETICBREW_URL="https://YOUR_SYNTHETICBREW_ORIGIN"export SYNTHETICBREW_TOKEN="bb_your_token"Enterprise operators testing on the host can instead use a local origin such as http://localhost:8443 if their network and TLS configuration allow it.
Send a streaming message
Section titled “Send a streaming message”POST /api/v1/schemas/{name}/chat requires chat scope. {name} is the stable configuration name returned by GET /api/v1/schemas, not the schema UUID.
curl -N "$SYNTHETICBREW_URL/api/v1/schemas/support-handbook/chat" \ -H "Authorization: Bearer $SYNTHETICBREW_TOKEN" \ -H "Content-Type: application/json" \ -d '{"message":"What is the return policy?"}'The request body is limited to 1 MB.
| Field | Required | Meaning |
|---|---|---|
message | One of message or resume_interrupt | A new user message. An empty string is not a message. |
resume_interrupt | One of message or resume_interrupt | An object containing interrupt_id and arbitrary JSON payload. Requires session_id. |
session_id | On resume | A UUID returned by the previous turn. Omit it to create a new session. |
stream | No | Defaults to true. Set to false for a collected JSON response. |
user_sub | Usually no | Optional visitor identifier for trusted API-token integrations. It is namespaced under the token; authenticated JWT subjects cannot be overridden. |
headers | No | Header values to forward to MCP tools. Only names allowed by each MCP server’s forward_headers configuration are taken from the HTTP request automatically. |
message and resume_interrupt are mutually exclusive. Sending both or neither returns 400.
Handle the SSE stream
Section titled “Handle the SSE stream”The response content type is text/event-stream. Each frame has an event name and a JSON payload:
| Event | Payload | Client behavior |
|---|---|---|
thinking | content | Optionally display model reasoning when available. |
message_delta | content | Append the chunk to the visible assistant response. |
message | content | Treat as the completed assistant message when emitted. |
tool_call | tool, call_id, optional arguments | Show that a tool started. arguments is the tool argument map. |
tool_result | tool, call_id, content, summary, has_error | Record the tool result and failure flag. |
confirmation | content, call_id, optional tool | Informational notification for confirm_before; see the caution below. |
interrupt_request | interrupt_id, content | Parse content as JSON, render the requested UI, and resume the same session. |
interrupt_resume | interrupt_id, content | Mark the rendered interrupt as answered without adding a duplicate user bubble. |
done | session_id and optional token fields | Save the session UUID and close the turn. |
error | content and optional code, message | Surface the error. The stream then ends. |
The optional fields on done are total_tokens, context_tokens, prompt_tokens, completion_tokens, and cached_prompt_tokens. They appear only when the runtime has those values.
Example:
event: thinkingdata: {"content":"I will check the policy."}
event: tool_calldata: {"tool":"knowledge_search","call_id":"call-7","arguments":{"query":"return policy"}}
event: tool_resultdata: {"tool":"knowledge_search","call_id":"call-7","content":"Returns are accepted within 30 days.","summary":"1 relevant passage","has_error":false}
event: message_deltadata: {"content":"Returns are accepted "}
event: message_deltadata: {"content":"within 30 days."}
event: donedata: {"session_id":"67b592d5-a76f-4d83-9090-1ff0342ee2c3","total_tokens":156}Do not rely on a standalone structured_output event. When an agent calls show_structured_output, the stream emits interrupt_request instead of the raw tool events, followed by interrupt_resume after the client answers.
Resume a human-in-the-loop interrupt
Section titled “Resume a human-in-the-loop interrupt”The content string in interrupt_request contains the persisted interrupt payload. Parse it as JSON and render the described form or structured block.
event: interrupt_requestdata: {"interrupt_id":"d2219e44-d2c3-41ac-a721-c8dd3f660e68","content":"{\"interrupt_id\":\"d2219e44-d2c3-41ac-a721-c8dd3f660e68\",\"kind\":\"structured_output\",\"schema\":{...}}"}Post the answer to the same schema chat endpoint:
curl -N "$SYNTHETICBREW_URL/api/v1/schemas/support-handbook/chat" \ -H "Authorization: Bearer $SYNTHETICBREW_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "session_id":"67b592d5-a76f-4d83-9090-1ff0342ee2c3", "resume_interrupt":{ "interrupt_id":"d2219e44-d2c3-41ac-a721-c8dd3f660e68", "payload":{"answers":[{"id":"leave_type","value":"vacation"}]} } }'Expected result: the stream emits interrupt_resume, the paused turn continues, and a final done event retains the same session ID.
| Condition | HTTP status |
|---|---|
Missing session_id on a resume | 400 |
| Unknown or cross-tenant interrupt | 404 |
| Interrupt belongs to another session | 403 |
| Interrupt was already resolved or abandoned | 409 |
Continue and manage sessions
Section titled “Continue and manage sessions”Save the UUID from done, then include it on the next message:
curl -N "$SYNTHETICBREW_URL/api/v1/schemas/support-handbook/chat" \ -H "Authorization: Bearer $SYNTHETICBREW_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "message":"Does that include sale items?", "session_id":"67b592d5-a76f-4d83-9090-1ff0342ee2c3" }'Session administration uses separate read and write scopes:
# List active sessions for one agent. Default per_page is 20; maximum is 100.curl "$SYNTHETICBREW_URL/api/v1/sessions?agent_name=support-agent&status=active&page=1&per_page=20" \ -H "Authorization: Bearer $SYNTHETICBREW_TOKEN"
# Read one session and its persisted events.curl "$SYNTHETICBREW_URL/api/v1/sessions/67b592d5-a76f-4d83-9090-1ff0342ee2c3" \ -H "Authorization: Bearer $SYNTHETICBREW_TOKEN"curl "$SYNTHETICBREW_URL/api/v1/sessions/67b592d5-a76f-4d83-9090-1ff0342ee2c3/messages" \ -H "Authorization: Bearer $SYNTHETICBREW_TOKEN"GET /api/v1/sessions accepts agent_name, user_sub, status, from, to, page, and per_page. Dates use RFC 3339 or YYYY-MM-DD. Valid stored statuses are active, completed, expired, and failed. The current session record does not retain an agent name, so agent_name is accepted but does not narrow results. Filter by user, status, or time, then correlate the session ID with events or the Tool Call Log when you need agent attribution.
{ "data": [ { "id": "67b592d5-a76f-4d83-9090-1ff0342ee2c3", "title": "Return policy", "schema_id": "3429747f-c6ba-4e18-a10c-a93b05193295", "user_sub": "website:visitor-42", "status": "active", "metadata": {"order_id": "ord-123"}, "created_at": "2026-08-18T08:00:00Z", "updated_at": "2026-08-18T08:03:00Z" } ], "total": 1, "page": 1, "per_page": 20, "per_page_max": 100, "total_pages": 1}POST /api/v1/sessions accepts optional id, title, schema_id, user_sub, and metadata. PUT /api/v1/sessions/{id} accepts title, status, and metadata; DELETE /api/v1/sessions/{id} removes the session. IDs must be UUIDs and metadata is capped at 16 KB. Use only the stored status values listed above; SyntheticBrew rejects unsupported values.
API tokens with sufficient scope and Admin sessions can manage all sessions in the workspace. A regular end-user token can access only sessions with its own user_sub; attempts to access another user’s session return 404 without revealing whether it exists.
Request one JSON response
Section titled “Request one JSON response”Set stream to false when the client does not need progressive output or human-in-the-loop widgets:
curl "$SYNTHETICBREW_URL/api/v1/schemas/support-handbook/chat" \ -H "Authorization: Bearer $SYNTHETICBREW_TOKEN" \ -H "Content-Type: application/json" \ -d '{"message":"Summarize the return policy.","stream":false}'{ "session_id": "67b592d5-a76f-4d83-9090-1ff0342ee2c3", "schema_id": "3429747f-c6ba-4e18-a10c-a93b05193295", "message": "Returns are accepted within 30 days.", "tool_calls": [ { "tool": "knowledge_search", "input": "{\"query\":\"return policy\"}", "output": "Returns are accepted within 30 days." } ]}The optional error field contains a turn error. Non-streaming collection does not expose an interrupt payload, so use streaming for show_structured_output workflows.
JavaScript client example
Section titled “JavaScript client example”EventSource cannot send the required POST request. Use fetch and parse the response stream:
const response = await fetch( `${process.env.SYNTHETICBREW_URL}/api/v1/schemas/support-handbook/chat`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.SYNTHETICBREW_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ message: 'Hello' }), },);
if (!response.ok || !response.body) { throw new Error(`SyntheticBrew returned HTTP ${response.status}`);}
const reader = response.body.getReader();const decoder = new TextDecoder();let buffer = '';let eventName = '';
while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() ?? '';
for (const line of lines) { if (line.startsWith('event: ')) eventName = line.slice(7); if (!line.startsWith('data: ')) continue;
const data = JSON.parse(line.slice(6)); if (eventName === 'message_delta') process.stdout.write(data.content); if (eventName === 'interrupt_request') { renderInterrupt(data.interrupt_id, JSON.parse(data.content)); } if (eventName === 'done') console.log('\nsession:', data.session_id); }}Handle errors and retries
Section titled “Handle errors and retries”| Status | Meaning |
|---|---|
400 | Invalid JSON, an invalid UUID, an oversized body, or invalid field combination. |
401 | Missing or invalid authentication. |
403 | The token lacks the required scope, or a resumed interrupt belongs to another session. |
404 | The schema is unavailable or chat-disabled, or the requested resource is not visible to this actor. |
409 | The interrupt was already resolved or abandoned. |
429 | A Cloud edge or an Enterprise reverse proxy applied its configured traffic policy. |
5xx | A service, model, or upstream tool failed. |
An in-stream failure arrives as error and closes the stream. Retry only requests that are safe for your workflow. Use exponential backoff for 429, 502, 503, and 504, honoring Retry-After when the edge or proxy supplies it.
SyntheticBrew does not expose a built-in per-key rate-limit configuration or a rate-limit usage endpoint. Cloud traffic controls are applied by the managed service; Enterprise customers configure any request-rate policy in their ingress or reverse proxy.
Inspect tool-call history
Section titled “Inspect tool-call history”Authenticated clients can query the tool-call log:
curl "$SYNTHETICBREW_URL/api/v1/audit/tool-calls?agent=support-agent&tool=knowledge_search&page=1&per_page=20" \ -H "Authorization: Bearer $SYNTHETICBREW_TOKEN"Filters are session_id, agent, tool, status (completed or failed), user_id, from, to, page, and per_page. The default page size is 50 and the maximum is 100. Results contain id, session_id, agent_name, tool_name, input, output, status, duration_ms, user_id, and created_at, plus pagination metadata.
Public discovery and Enterprise metrics
Section titled “Public discovery and Enterprise metrics”The model registry is anonymous and read-only:
curl "$SYNTHETICBREW_URL/api/v1/models/registry?provider=anthropic"curl "$SYNTHETICBREW_URL/api/v1/models/registry/providers"Enterprise deployments can expose Prometheus-format metrics at /metrics. Restrict that endpoint through network policy or your reverse proxy.