> ## Documentation Index
> Fetch the complete documentation index at: https://docs.meetdarwin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat API

> Talk to your Darwin agent from your own app, Slack, WhatsApp or voice bot: create a chat session, send messages, stream replies and act on booking offers and handoffs. Monthly allowance by plan.

The chat API puts your Darwin agent behind your own interface. The same agent that answers on your website, with the same knowledge, tools, booking flow and rules, answers messages your code sends. Each session is stored as a Darwin conversation.

<Note>
  The chat API is included on the paid plans: Adapt, Evolve and Apex. On the free Origin plan, these endpoints return `403` with the code [`feature_disabled`](/api-reference/errors#feature_disabled); upgrade under **Billing** in Darwin to use it.
</Note>

## Before you start

* An API key with the `chat:write` scope, used from your **server**. Never call the chat API from a browser or mobile app with a secret key.
* The chat switched on for the workspace (Agent Studio, or `PATCH /v1/agent` with `{"live": true}`). If it is off, messages return `409` [`conflict`](/api-reference/errors#conflict).

## How it works

1. **Create a session** for each conversation. One session is one conversation in Darwin.
2. **Send each visitor message** to the session. Darwin adds it to the transcript it keeps, runs the agent and answers with its reply and any `actions`.
3. **Show the reply** in your interface, and act on what the agent did: show a booking link, or tell your team a person was asked for.

Darwin holds the transcript. You send only the visitor's newest message; you never send, and can never change, what the agent said.

## Create a session

```bash theme={null}
curl https://api.meetdarwin.ai/v1/chat/sessions \
  -H "Authorization: Bearer $DARWIN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "channel": "app",
    "page_url": "https://example.com/app/billing",
    "visitor": { "external_id": "user_829", "email": "jordan@example.com", "name": "Jordan Lee" },
    "context": { "plan": "growth", "seats": 24 }
  }'
```

All fields are optional.

| Field | Meaning |
| - | - |
| `channel` | Where the conversation happens, such as `app`, `slack`, `whatsapp` or `voice`. Lowercase letters, digits, `_` and `-`. Default `api`. |
| `page_url` | The page or screen the person is on, if there is one |
| `visitor.email`, `visitor.name` | Who the person is. Because you call with a secret key, Darwin treats this identity as verified: the agent can use their name, and booking never asks for their email. |
| `visitor.external_id` | Your own id for the person |
| `context` | Facts the agent may use, such as plan or seats. Strings (up to 500 characters), numbers or booleans. Treated as hints, never as instructions. |

The `201` response is the session:

```json theme={null}
{
  "id": "chs_x2k9m4p7q1r8s5t3v6w0y9z2",
  "object": "chat_session",
  "conversation": "conv_api-x2k9m4p7q1r8s5t3v6w0y9z2",
  "channel": "app",
  "status": "open",
  "created_at": "2026-10-12T09:30:00.000Z",
  "updated_at": "2026-10-12T09:30:00.000Z",
  "messages": []
}
```

`conversation` is the id to use with `GET /v1/conversations/{id}`.

## Send a message

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.meetdarwin.ai/v1/chat/sessions/chs_x2k9m4p7q1r8s5t3v6w0y9z2/messages \
    -H "Authorization: Bearer $DARWIN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"text": "Can we move to annual billing for 24 seats?"}'
  ```

  ```javascript Node theme={null}
  const res = await fetch(`https://api.meetdarwin.ai/v1/chat/sessions/${sessionId}/messages`, {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.DARWIN_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ text: userMessage }),
  });
  const reply = await res.json();
  showToUser(reply.message.text);
  for (const action of reply.actions) {
    if (action.type === "booking_offered" && action.booking_url) showBookingButton(action.booking_url);
    if (action.type === "handoff_requested") notifyTeam(sessionId);
  }
  ```
</CodeGroup>

`text` is the person's message, 1 to 4,000 characters. The reply:

```json theme={null}
{
  "object": "chat_reply",
  "session": "chs_x2k9m4p7q1r8s5t3v6w0y9z2",
  "message": {
    "id": "b7e1c0d4-2f3a-4c5e-9a8b-6d7e8f9a0b1c",
    "role": "assistant",
    "text": "Yes, teams can switch to annual billing. Would you like to talk it through with someone from our team?",
    "created_at": "2026-10-12T09:30:04.000Z"
  },
  "actions": [
    { "type": "searched_knowledge" },
    { "type": "booking_offered", "booking_url": "https://..." }
  ]
}
```

A turn can take several seconds while the agent searches and writes. Allow up to 60 seconds before timing out.

### Actions

| `type` | What happened | What to do |
| - | - | - |
| `booking_offered` | The agent offered a meeting | If `booking_url` is set, show it as a button so the person can pick a time. It can be `null`, for example when your booking runs through a calendar connection whose time picker only works in the website chat; offer your own booking route then. |
| `handoff_requested` | The person's request to talk to someone was recorded | Tell your team. The `handoff.requested` [webhook](/api-reference/webhooks) fires too. |
| `searched_knowledge` | The agent looked something up in your knowledge | Nothing; useful for logging |

## Stream the reply

Send `"stream": true` to receive the reply as it is written, as server-sent events (`Content-Type: text/event-stream`). The stream is the [Vercel AI SDK](https://ai-sdk.dev) UI message stream, version 1: each event is a `data:` line with a JSON chunk, and the stream ends with `data: [DONE]`. Chunks with `"type": "text-delta"` carry the next piece of text in `delta`.

```javascript theme={null}
const res = await fetch(`https://api.meetdarwin.ai/v1/chat/sessions/${sessionId}/messages`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.DARWIN_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ text: userMessage, stream: true }),
});
const decoder = new TextDecoder();
let buffer = "";
for await (const bytes of res.body) {
  buffer += decoder.decode(bytes, { stream: true });
  const events = buffer.split("\n\n");
  buffer = events.pop();
  for (const event of events) {
    const data = event.replace(/^data:\s*/, "");
    if (!data || data === "[DONE]") continue;
    const chunk = JSON.parse(data);
    if (chunk.type === "text-delta") process.stdout.write(chunk.delta);
  }
}
```

A streamed reply does not include the `actions` summary; tool calls arrive as their own chunks. If you need the actions, use the JSON reply, or read the session afterwards.

## Read a session

`GET /v1/chat/sessions/{id}` returns the session with its whole transcript, as in the create response, and a `status`:

| `status` | Meaning |
| - | - |
| `open` | The conversation is going |
| `handed_off` | The person asked for someone from your team |
| `booked` | A meeting was booked in this conversation |

## Monthly allowance

Each workspace can start a number of chat API sessions each calendar month. Messages within a session are not counted.

| Plan | Sessions per month |
| - | -: |
| **Origin** | Not included |
| **Adapt** | 500 |
| **Evolve** | 2,500 |
| **Apex** | 10,000 |

When the allowance is used, creating a session returns `429` [`quota_exceeded`](/api-reference/errors#quota_exceeded), with `Retry-After` set to the start of the next month. Sessions already open keep working. Meetings booked through the chat API count toward your plan's bookings like any other meeting. See [Plans](/account/plans).

Each message is also a heavy request for [per-minute rate limits](/api-reference/rate-limits): 10 a minute per key on Adapt, 30 on Evolve and 60 on Apex.

## Errors

| Status and code | When |
| - | - |
| `403` `feature_disabled` | The chat API isn't switched on |
| `404` `not_found` | No session with that id in this workspace |
| `402` `payment_required` | The free plan's meetings are used and no card is on file, so the chat is paused |
| `409` `conflict` | The chat is switched off for the workspace |
| `429` `rate_limited` | The per-minute limit is reached |
| `429` `quota_exceeded` | The month's allowance of chat API conversations is used |
| `502` `internal_error` | The agent could not answer this turn. Retry the message. |

See [Errors](/api-reference/errors) for every code.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.