SynapseOrch 2.0 is a release candidate; the stable release is 1.9.3. How to try 2.0

Chat API

Chat with an agent over the REST API, as one synchronous request or a server-sent event stream, and list the agents you can call.

The reference has a page for each of these endpoints, with every field and answer and samples in cURL, JavaScript, Python and Go: send, stream, cancel, approve, and the agents' list and get. /api/v2's chat starts at Send a chat message.

POST /api/v1/chat

Send a message to an agent and get a synchronous response. The request waits until the agent completes its full ReAct loop and returns the final answer. If the agent is an Orchestrator agent, its workflow runs and its final response is returned.

Request

POST /api/v1/chat
Authorization: Bearer sk-syn-...
Content-Type: application/json
{
  "message": "What is the weather in London right now?",
  "agent": "Research Agent",
  "session_id": "user-session-123",
  "images": []
}

Request body fields:

FieldTypeRequiredDescription
messagestringYesThe user's message. @[path] vault mentions are read in, as in Chat
agentstringNoAgent ID, or its exact name in any case. An unknown agent is a 404; omitted, the workspace's first agent answers — see Agent resolution
session_idstringNoSession identifier for conversation continuity. Omitted, a new one (api_…) is generated, and the conversation starts fresh
imagesarrayNoBase64 data-URI images for models that can see them — PNG, JPEG, GIF or WebP; at most 5, each up to 5 MB, 20 MB in total. e.g. "data:image/png;base64,...". An image that breaks these rules is a 422, and so are images for an agent that can't read them: This agent's model (…) cannot read images. Send the message without them, or choose a model that can for this agent. — refused before anything is queued

Response

{
  "response": "The current weather in London is 12°C and cloudy...",
  "status": "completed",
  "agent_id": "agent_1726412345678",
  "agent_name": "Research Agent",
  "session_id": "user-session-123",
  "refused_calls": []
}

Response fields:

FieldTypeDescription
responsestringThe agent's final response text — or, when the turn stopped for a person, the question it stopped on
statusstringcompleted, awaiting_approval or paused — see below — or cancelled, when the turn was stopped
agent_idstringID of the agent that responded
agent_namestringDisplay name of the agent
session_idstringSession ID — send it with the next request to continue the conversation
refused_callsarrayThe tool calls this turn did not run, each {tool_name, category, reason} — refused by Tool Policy or by a person. Always present; empty when every call ran. The answer is the model's own words and can say something was done when its only call was refused, so check this list
citationsarrayThe knowledge passages the answer cited, each with its label, file, pages, section, snippet, when it was indexed and its quote check — see Search. Always present; empty when it cited none
unresolved_citationsarrayLabels the answer cited that no search in this turn returned

If the agent fails, the request returns 500, and the details are in the server log and in Runs › Logs. The provider's error is never returned as the agent's response. The body says which kind of failure it was:

  • The model failed. detail starts The agent encountered an error processing your request. and adds a sentence for the kind, with code and retryable beside it:

    codeMeansWhat to do
    quota_exhaustedThe provider refused it: the credit or quota is exhaustedTop up or raise the limit with the provider; retrying will not help
    provider_refusedThe provider refused it — a key it does not accept, a model it does not serveFix the agent's model or the workspace's model settings
    rate_limitedThe provider is limiting requestsRetry shortly
    provider_unavailableThe provider could not be reached, or failedRetry shortly
    model_no_answerThe model answered with nothing usable — empty, blocked, or only reasoningTry another model, or rephrase
    model_errorAny other model failure (no sentence added)Read the turn in the app's logs

    retryable is true for rate_limited and provider_unavailable only.

    {
      "detail": "The agent encountered an error processing your request. The model provider is limiting requests right now. Try again shortly.",
      "code": "rate_limited",
      "retryable": true
    }
  • An Orchestrator agent's workflow failed. detail names the run to read back: The agent's workflow failed before answering. Read run <run_id> at /api/v1/orchestrations/runs/<run_id>. See reading a run.

  • Anything else. detail is The agent encountered an error processing your request., with no code.

A session writes one reply at a time: a message to a session whose turn is still running returns 409 (A reply is still being written in this conversation).

When the turn stops for a person

awaiting_approval — the agent reached a tool call its Tool Policy wants confirmed. The call has not run. pending_call describes it:

{
  "response": "The agent wants to run **scrape_url** — it reaches systems outside this workspace. Allow it?",
  "status": "awaiting_approval",
  "pending_call": {
    "tool_name": "scrape_url",
    "tool_args": { "url": "https://example.com" },
    "category": "open_world",
    "approval_id": "3f9a1c2b7d4e5f60"
  },
  "agent_id": "agent_1726412345678",
  "agent_name": "Research Agent",
  "session_id": "user-session-123"
}

paused — the agent is an Orchestrator agent and its workflow paused on a Human step. run_id names the run, and response holds the step's question:

{
  "response": "Review the draft and approve or reject it.",
  "status": "paused",
  "run_id": "run_orch_k3j9x2a_1734567890123",
  "agent_id": "agent_1726412345678",
  "agent_name": "Report Pipeline",
  "session_id": "user-session-123"
}

Answer a call waiting for approval with POST /api/v1/chat/{session_id}/approve, quoting its approval_id. A paused workflow is answered through its resume endpoint with the run_id, or in the app.

Sending the conversation a new message instead of answering refuses the waiting call: it is not run, the agent is told, and the call cannot be allowed afterwards — an approve sent later is a 409.

cURL example

curl -X POST http://localhost:3000/api/v1/chat \
  -H "Authorization: Bearer sk-syn-..." \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Summarise the latest news about AI agents",
    "agent": "Research Agent",
    "session_id": "my-session"
  }'

Python example

import requests

response = requests.post(
    "http://localhost:3000/api/v1/chat",
    headers={"Authorization": "Bearer sk-syn-..."},
    json={
        "message": "What is 15% of $2,340?",
        "agent": "Finance Assistant",
        "session_id": "calc-session",
    },
)
data = response.json()
print(data["response"])

POST /api/v1/chat/stream

Send a message to an agent and receive a stream of Server-Sent Events (SSE). This lets you display progress and tool calls in real time.

Request

Same as /chat — same fields, same auth.

SSE Event Stream

The response is a stream of data: events, each a JSON object, plus : ping comment lines after 10 seconds without an event — skip any line that starts with :.

Event types:

TypeDescriptionFields
sessionFirst event — session metadatasession_id, agent_id, agent_name
statusStatus updatemessage
thinkingA progress message, such as "Analyzing your request..." — not the model's reasoningmessage
tool_executionAgent is calling a tooltool_name, args
tool_resultTool call completed — or was not run: then refused is true, with the reason and the policy categorytool_name, preview; refused, reason, category when not run
llm_thoughtThe agent's reasoning text for a turnthought, turn
answer_deltaThe answer as the model writes it; response is still the answer of recordtext, turn
answer_resetWhat streamed since the last reset was not the answer (the model went on to call a tool): drop itreason, turn
knowledge_searchThe agent searched its knowledge basesquery_id, kb_ids, query, hit_count, labels, degraded, cost_usd, latency_ms
knowledge_readThe agent read around a passagelabel, path, window
knowledge_citationsJust before response: the passages the answer cited — only this turn's own, not a Team member'scitations, unresolved_citations
responseFinal responsecontent, intent, session_id
awaiting_approvalA tool call is waiting for approval and has not run — see When the turn stops for a personprompt, pending_call, session_id
pausedAn Orchestrator agent's workflow paused on a Human stepprompt, run_id, session_id
agent_step_resultAn Orchestrator agent's workflow finished one of its agent steps — not the turnorch_step_id, step_name, content
cancelledThe turn was stoppedmessage
errorThe agent failed. The message says which kind of failure it was, as for /chat; a model failure adds code and retryable, a failed workflow adds run_id. The provider's own words are never sentmessage; code, retryable or run_id
doneThe last event: after response, awaiting_approval, paused, cancelled or error—

A stream that ends with error has no response. Other events the engine produces — task list updates, for example — are not forwarded.

cURL example

curl -X POST http://localhost:3000/api/v1/chat/stream \
  -H "Authorization: Bearer sk-syn-..." \
  -H "Content-Type: application/json" \
  -N \
  -d '{"message": "Research quantum computing trends", "agent": "Research Agent", "session_id": "stream-test"}'

Example stream output:

data: {"type": "session", "session_id": "stream-test", "agent_id": "agent_1726412345678", "agent_name": "Research Agent"}

data: {"type": "thinking", "message": "Analyzing your request..."}

data: {"type": "tool_execution", "tool_name": "scrape_url", "args": {"url": "https://arxiv.org/..."}}

data: {"type": "tool_result", "tool_name": "scrape_url", "preview": "Quantum computing in 2024..."}

data: {"type": "response", "content": "Here are the key trends in quantum computing...", "intent": "chat", "session_id": "stream-test"}

data: {"type": "done"}

JavaScript example

const response = await fetch("http://localhost:3000/api/v1/chat/stream", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk-syn-...",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    message: "Write a Python script to parse JSON",
    agent: "Code Assistant",
    session_id: "js-session",
  }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";

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(); // keep a partial line for the next chunk

  for (const line of lines) {
    if (!line.startsWith("data: ")) continue; // skips ": ping" and blank lines
    const event = JSON.parse(line.slice(6));
    if (event.type === "response") {
      console.log("Final:", event.content);
    } else if (event.type === "tool_execution") {
      console.log("Using tool:", event.tool_name);
    }
  }
}

The turn is not the connection

A turn belongs to the server, not to the request that started it. A caller that stops reading — a timeout, a crash, a dropped network — leaves the turn running to the end, and its answer is saved to the session. To stop a turn, cancel it.


POST /api/v1/chat/{session_id}/cancel

Stop the turn running in a session.

curl -X POST http://localhost:3000/api/v1/chat/stream-test/cancel \
  -H "Authorization: Bearer sk-syn-..."
{"status": "cancellation_requested", "session_id": "stream-test", "stopped": true}

stopped is false when nothing was running — the turn had already finished. That is not an error. A turn inside a long model call stops when that call returns; its stream ends with cancelled, then done.


POST /api/v1/chat/{session_id}/approve

Answer the tool call a conversation stopped on (status: "awaiting_approval"), and carry the turn on. The answer is a stream, in the same events as /chat/stream.

curl -X POST http://localhost:3000/api/v1/chat/user-session-123/approve \
  -H "Authorization: Bearer sk-syn-..." \
  -H "Content-Type: application/json" \
  -N \
  -d '{"approve": true, "approval_id": "3f9a1c2b7d4e5f60"}'
FieldTypeRequiredDescription
approvebooleanYestrue runs the call; false refuses it
reasonstringNoWhy it was refused — the agent is told
approval_idstringNoThe approval_id from pending_call. When given and it is not the call the conversation is waiting on, the answer is a 409 rather than an answer to a newer question
agentstringNoThe conversation's agent. Omitted, the agent that is waiting; name it (or the approval_id) when two agents in one conversation both wait

Approved, the call runs and the stream carries on to the answer. Refused, the stream shows the call as tool_execution, then a tool_result with refused, reason and category, and the agent carries on without it.

A conversation that is not waiting on an approval answers 409 (This conversation is not waiting on an approval.), and so does one whose turn is still running.


GET /api/v1/agents

List the workspace's agents. Synapse AI's own agents are not listed, and the chat endpoints never run them.

curl http://localhost:3000/api/v1/agents \
  -H "Authorization: Bearer sk-syn-..."
[
  {
    "id": "agent_1726412345678",
    "name": "Research Agent",
    "type": "conversational",
    "model": "claude-opus-5",
    "capabilities": []
  }
]

model is empty when the agent uses the default model. capabilities is always an empty list.


GET /api/v1/agents/{agent_id}

Get details for a specific agent. Returns 404 if there is no agent with that ID, or if it is one of Synapse AI's own.

curl http://localhost:3000/api/v1/agents/agent_1726412345678 \
  -H "Authorization: Bearer sk-syn-..."
{
  "id": "agent_1726412345678",
  "name": "Research Agent",
  "type": "conversational",
  "model": "claude-opus-5",
  "capabilities": [],
  "description": "Deep research using web browsing"
}

On this page