Agents API

Invoke Coherence AI agents programmatically with a single synchronous call

The Agents API lets you send a natural-language message to a Coherence agent and receive its final response in a single synchronous call. The agent runs its multi-step tool loop server-side — querying records, searching your workspace, drafting content, creating tasks — and returns a natural-language answer when it finishes.

Any tool the agent calls flows through your workspace's approval system, exactly as it does in the app. An API caller cannot use an agent to bypass approval gates: gated actions (sending email, external side effects, and so on) are queued for approval rather than executed directly.

Send a Message

POST https://api.getcoherence.io/v1/agents/messages

Requires an API key with the agents:write scope. See Authentication.

The endpoint runs in two modes over the same request: a synchronous JSON response by default, or an SSE stream when you opt in with an Accept: text/event-stream header or a ?stream=true query parameter. The request body and scope are identical in both modes.

Request Body

FieldTypeRequiredDescription
messagestringYesThe instruction or question for the agent (1–10,000 characters)
agentIdstring (UUID)NoTarget a specific agent in your workspace. Omit to use the workspace's default agent.

Example Request

curl -X POST "https://api.getcoherence.io/v1/agents/messages" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "How many open deals do we have over $50k, and who owns them?"
  }'

Example Response

{
  "response": "You have 4 open deals over $50k. Acme Corp ($120k) and Initech ($75k) are owned by Sarah Chen; Globex ($64k) and Hooli ($52k) are owned by Mike Johnson.",
  "success": true,
  "durationMs": 8421,
  "toolCalls": 3
}

Response Fields

FieldTypeDescription
responsestringThe agent's final natural-language answer
successbooleanWhether the agent run completed successfully
durationMsnumberServer-side duration of the run in milliseconds
toolCallsnumberNumber of tool invocations the agent made
errorstringPresent only when the run failed

Behavior Notes

  • Synchronous — the request stays open while the agent works. Runs involving several tool calls can take tens of seconds; set your client timeout to at least 60 seconds.
  • Bounded — each call is capped at a fixed number of tool-loop steps, so a single request completes within roughly the timeout window of typical HTTP and MCP clients. For long multi-part work, send follow-up messages rather than one giant instruction.
  • Approval gates apply — if the agent attempts a gated action, the action is queued for approval in your workspace and the agent reports that in its response instead of performing it.
  • Agent targeting — when agentId is provided, it must reference an agent in your workspace; otherwise the request fails with 404.

Errors

Errors use the standard Coherence envelope (see Error Handling):

{
  "error": {
    "code": "not_found",
    "message": "Agent not found",
    "statusCode": 404
  }
}
StatusWhen
400message missing, empty, or over 10,000 characters; agentId not a valid UUID
401Missing or invalid API key
403API key lacks the agents:write scope
404agentId does not match an agent in your workspace

Streaming responses

The same endpoint can stream the run over Server-Sent Events (SSE) so you can render the agent's answer token-by-token and observe tool calls as they happen, instead of waiting for the whole run to finish.

Opt in either way — the request body and agents:write scope are unchanged:

  • Send an Accept: text/event-stream request header, or
  • Add a ?stream=true query parameter to the URL.

The response comes back with Content-Type: text/event-stream as a sequence of named events. Each event is an event: line followed by a data: line containing a JSON object:

Eventdata shapeMeaning
open{ "agentId": string | null }The stream is established. agentId echoes the resolved target agent, or null for the workspace default.
token{ "text": string }A chunk of the agent's final answer. Concatenate text across all token events to reconstruct the full answer.
tool_call{ "tool": string, "label": string }The agent invoked a tool. label is a human-readable description such as "Searching records". No tool arguments or results are exposed.
done{ response, success, durationMs, toolCalls, error? }Terminal success. The data is the same object as the synchronous JSON body (see Response Fields).
error{ "message": string }Terminal failure. The stream ends after this event.

Privacy parity with the synchronous response. Only the agent's final answer (token) and human-readable tool labels (tool_call) are streamed. Raw model reasoning and raw tool output are never sent over the stream, exactly as they are never included in the synchronous response.

Lines beginning with : are keepalive comments (sent roughly every 15 seconds to hold the connection open) — ignore them. If the client disconnects, the run is aborted server-side.

Streaming with curl

Use -N so curl doesn't buffer the stream, and set the Accept header:

curl -N -X POST "https://api.getcoherence.io/v1/agents/messages" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
    "message": "How many open deals do we have over $50k, and who owns them?"
  }'

The response is a stream of events (keepalive comments elided):

event: open
data: {"agentId":null}

event: tool_call
data: {"tool":"search_records","label":"Searching records"}

event: token
data: {"text":"You have 4 open deals over $50k. "}

event: token
data: {"text":"Acme Corp ($120k) and Initech ($75k) are owned by Sarah Chen."}

event: done
data: {"response":"You have 4 open deals over $50k. Acme Corp ($120k) and Initech ($75k) are owned by Sarah Chen.","success":true,"durationMs":8421,"toolCalls":3}

Streaming with fetch (JavaScript / TypeScript)

You cannot use the browser EventSource API. EventSource only issues GET requests and can't send a request body or an Authorization header, both of which this endpoint requires. Use fetch with a ReadableStream reader (as below) on the server or in the browser instead.

const res = await fetch('https://api.getcoherence.io/v1/agents/messages', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
    Accept: 'text/event-stream',
  },
  body: JSON.stringify({
    message: 'How many open deals do we have over $50k, and who owns them?',
  }),
});
 
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
let answer = '';
 
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
 
  // SSE frames are separated by a blank line.
  const frames = buffer.split('\n\n');
  buffer = frames.pop() ?? '';
 
  for (const frame of frames) {
    let eventName = 'message';
    const dataLines: string[] = [];
 
    for (const line of frame.split('\n')) {
      if (line.startsWith(':')) continue; // keepalive comment
      if (line.startsWith('event:')) eventName = line.slice(6).trim();
      else if (line.startsWith('data:')) dataLines.push(line.slice(5).trim());
    }
 
    if (dataLines.length === 0) continue;
    const data = JSON.parse(dataLines.join('\n'));
 
    if (eventName === 'token') {
      answer += data.text; // concatenate token chunks
    } else if (eventName === 'tool_call') {
      console.log(`Agent is: ${data.label}`);
    } else if (eventName === 'done') {
      console.log('Final answer:', data.response);
    } else if (eventName === 'error') {
      throw new Error(data.message);
    }
  }
}

The full answer is available two ways: accumulate token events (answer above), or read response from the final done event — they resolve to the same text.


Related: API Overview | Authentication | Records API