# Agent chat

> Talk to one agent, or let Subsidia pick the best-fit agent: multi-turn sessions, knowledge citations, tool calls, structured JSON and a server-sent event stream.

Agent chat is the endpoint family for **conversations with a persona**: the agent answers in character, remembers past sessions, reads the documents linked to it, calls your HTTP tools when it needs live data, and can ask a colleague for a second opinion. It is the right choice when your application is built around a specific agent. If you only want to point an existing OpenAI or Anthropic client at an agent, use the [Gateway](https://dev.subsidia.protypa.fr/docs/gateway.md) with the model name `agent:<slug>` instead.

There are two styles, each with a plain and a streamed form:

| Style | Plain | Streamed | Use when |
| --- | --- | --- | --- |
| A specific agent | `POST /v1/agents/:id/chat` | `POST /v1/agents/:id/chat/stream` | Your application knows which agent should answer. |
| Auto-pick | `POST /v1/agents/chat` | `POST /v1/agents/chat/stream` | You have several agents and want the best fit for each message, for example behind a single input box. |

**One turn. The steps with a dashed role (knowledge, tools, consult) are skipped when the corresponding block is disabled or the agent has nothing linked.**

Flow: Message -> Session + history -> Memory recall -> Knowledge retrieval -> Tools + consult -> Answer -> Memory, facts, proof

> **INFO: Postes cannot be chatted with**
> A poste is a bounded job, not a conversation partner. Chat refuses it with a 500 whose message says so; run it with [`POST /v1/postes/:key/run`](https://dev.subsidia.protypa.fr/docs/postes.md). Auto-pick considers every agent of the workspace, so in a workspace that has installed postes, address the agent you want explicitly.

## Authentication, scopes and billing

Use a developer API key ([Authentication](https://dev.subsidia.protypa.fr/docs/authentication.md)). On a restricted key, these routes need the `agents` scope alongside your capability scope; a `readonly` key is refused because chatting writes (history, memory). Each chat route is also limited to 30 calls per minute per client, and a key can carry its own per-minute limit and monthly ceiling ([Rate limits and quotas](https://dev.subsidia.protypa.fr/docs/rate-limits.md)).

Each turn is metered against the workspace allowance of questions on the model usage of the whole turn (retrieval, tool rounds and consultations included), and an exhausted allowance is a `402` before any model runs ([Rate limits and quotas](https://dev.subsidia.protypa.fr/docs/rate-limits.md)).

## A first call

1. **Pick an agent**

   List your agents with [`GET /v1/agents`](https://dev.subsidia.protypa.fr/docs/agents.md) and keep the `id`, or create one.

2. **Send a message**

   Without a `sessionId`, a new session is created for you and its id is returned.

   ```bash
   curl https://api.subsidia.protypa.fr/v1/agents/$AGENT_ID/chat \
     -H "Authorization: Bearer $SUBSIDIA_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
       "message": "What is the Q3 invoice total, and is the file complete?",
       "context": [
         { "category": "client", "label": "Client record", "data": { "name": "Atelier Moreau", "plan": "monthly" }, "priority": "high" }
       ],
       "knowledge": { "topK": 5 }
     }'
   ```

3. **Continue the conversation**

   Send the returned `sessionId` back. The agent sees the last 20 messages of the session, so it can answer "and the previous quarter?" without being told what "it" refers to.

   ```bash
   curl https://api.subsidia.protypa.fr/v1/agents/$AGENT_ID/chat \
     -H "Authorization: Bearer $SUBSIDIA_API_KEY" -H "Content-Type: application/json" \
     -d '{"message": "And the previous quarter?", "sessionId": "cm2kc4t9p000bqz0fr6s2a8dk"}'
   ```

## Endpoints

### POST /v1/agents/:id/chat

Send a message to one agent and get the full answer.

Runs one turn with the agent in `:id`: recalls its memories, loads the session history, retrieves passages from its linked knowledge sources, lets it call its tools and consult colleagues, produces the answer, then saves the exchange, any new memories and the extracted facts. The call returns when the answer is complete; for long answers use the streamed form below.

Knowledge, tools and consultation are **on by default**, exactly like in the Subsidia app. An agent that has no linked sources or tools simply does not use them. Pass `enabled: false` in the matching block for an isolated call.

- **Authentication:** API key (Bearer or x-api-key)
- **Scopes:** `agents`

#### Path parameters

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | yes |  | The agent id. |

#### Request body

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `message` | `string` | yes |  | What you say to the agent. Must be a non-empty string. |
| `sessionId` | `string` | no |  | Continue an existing session ([Sessions](https://dev.subsidia.protypa.fr/docs/sessions.md)). Omit it to start a new one: its id comes back in the response. A session that does not exist, or that was started by another user, is a 404 `Session not found`. |
| `context` | `object[]` | no |  | Structured data injected into the agent prompt for this call only (it is not stored). See [Context injection](#context-injection). |
| `context.category` | `string` | yes |  | What kind of data this is, for example `client`, `invoice`, `crm`. Used as the block heading when `label` is absent. |
| `context.label` | `string` | no |  | Heading shown to the model instead of the category. |
| `context.data` | `object | string` | yes |  | The data. Objects are serialised to indented JSON. |
| `context.priority` | `"high" | "medium" | "low"` | no | `medium` | Blocks are ordered high first; `high` blocks are flagged as high priority to the model. One of: `high`, `medium`, `low`. |
| `extraction` | `object` | no |  | Extract structured facts from your message, alongside the answer. See [Fact extraction](#fact-extraction). |
| `extraction.categories` | `string[]` | yes |  | Categories to extract, at least one, for example `["company_info", "decisions", "preferences"]`. |
| `extraction.persist` | `boolean` | no | `true` | Store the extracted facts. Set `false` to only return them. |
| `options` | `object` | no |  | Generation settings. |
| `options.reflection` | `boolean` | no | `false` | Run a separate reasoning pass before the answer and return it as `thoughts`. Skipped for small talk. Adds latency and cost. |
| `options.temperature` | `number` | no | `0.8` | Sampling temperature, 0 to 2. |
| `options.maxTokens` | `number` | no | `1500` | Output ceiling, 100 to 4000. |
| `responseFormat` | `object` | no |  | Force a structured JSON answer. See [Structured JSON answers](#structured-json-answers). |
| `responseFormat.type` | `"json" | "text"` | yes |  | `json` forces a JSON object; `text` is the default prose mode. One of: `json`, `text`. |
| `responseFormat.fields` | `object` | no |  | Map of field name to a description of what it must contain. |
| `responseFormat.schema` | `string` | no |  | A free-form schema description, as an alternative to `fields`. |
| `knowledge` | `object` | no |  | Retrieval from the knowledge sources linked to the agent. On by default. |
| `knowledge.enabled` | `boolean` | no | `true` | Set `false` to answer without reading any document. |
| `knowledge.topK` | `number` | no | `5` | Passages to retrieve, 1 to 20. Questions that ask for a list (who are our clients) use 8 when you do not set it. |
| `tools` | `object` | no |  | The HTTP tools linked to the agent ([Tools](https://dev.subsidia.protypa.fr/docs/tools.md)). On by default. |
| `tools.enabled` | `boolean` | no | `true` | Set `false` so the agent never calls a tool in this turn. |
| `tools.toolIds` | `string[]` | no |  | Restrict the turn to a subset of the linked tools. Omit to allow all of them. Inactive tools are never offered. |
| `consult` | `object` | no |  | Let the agent ask other agents of the workspace for a second opinion mid-turn. See [Consulting colleagues](#consulting-colleagues). |
| `consult.enabled` | `boolean` | no | `true` | Set `false` for an isolated call. |

#### Request examples

_curl_

```bash
curl https://api.subsidia.protypa.fr/v1/agents/$AGENT_ID/chat \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "What is the Q3 invoice total, and is the file complete?",
    "context": [
      { "category": "client", "label": "Client record", "data": { "name": "Atelier Moreau", "plan": "monthly" }, "priority": "high" }
    ],
    "knowledge": { "topK": 5 }
  }'
```

_TypeScript_

```typescript
const res = await fetch('https://api.subsidia.protypa.fr/v1/agents/' + agentId + '/chat', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.SUBSIDIA_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    message: 'What is the Q3 invoice total, and is the file complete?',
    context: [
      { category: 'client', label: 'Client record', data: { name: 'Atelier Moreau', plan: 'monthly' }, priority: 'high' },
    ],
    knowledge: { topK: 5 },
  }),
})
if (!res.ok) throw new Error(res.status + ' ' + (await res.text()))
const turn = await res.json()

console.log(turn.content)
for (const k of turn.knowledgeUsed ?? []) console.log('[' + k.ref + ']', k.sourceName, 'p.' + k.page)
const nextSessionId = turn.sessionId // send it back to continue
```

_Python_

```python
import os, requests

res = requests.post(
    "https://api.subsidia.protypa.fr/v1/agents/" + agent_id + "/chat",
    headers={"Authorization": "Bearer " + os.environ["SUBSIDIA_API_KEY"]},
    json={
        "message": "What is the Q3 invoice total, and is the file complete?",
        "context": [
            {"category": "client", "label": "Client record",
             "data": {"name": "Atelier Moreau", "plan": "monthly"}, "priority": "high"},
        ],
        "knowledge": {"topK": 5},
    },
    timeout=300,
)
res.raise_for_status()
turn = res.json()
print(turn["content"])
for k in turn.get("knowledgeUsed", []):
    print("[%d] %s p.%s" % (k["ref"], k["sourceName"], k["page"]))
session_id = turn["sessionId"]
```

#### Responses

**200**: The turn. `content` is the answer; `contentParsed` is present only with `responseFormat.type: "json"` and valid JSON; `thoughts` only with `options.reflection`; `knowledgeUsed`, `toolCalls` and `memoriesSaved` are empty or absent when nothing was used or saved. `tokenUsage` is the model usage of the whole turn, internal steps included.

```json
{
  "agentId": "cm2k8x1ab0001qz0f7h3d9t4e",
  "agentName": "Claire",
  "agentRole": "Contrôle de gestion",
  "content": "The Q3 invoice is 12 480 EUR excluding VAT [1]. It matches the purchase order, but the delivery note is missing from the file.",
  "sessionId": "cm2kc4t9p000bqz0fr6s2a8dk",
  "memoriesSaved": [
    { "key": "q3_invoice_total", "value": "Q3 invoice: 12 480 EUR excl. VAT.", "type": "fact", "provenance": "knowledge", "salience": 0.65 }
  ],
  "extracted": [],
  "tokenUsage": { "promptTokens": 2140, "completionTokens": 62, "totalTokens": 2202 },
  "knowledgeUsed": [
    {
      "ref": 1,
      "sourceId": "cm2k9aa7e0003qz0fh8w1p2lb",
      "sourceName": "facture-q3-2026.pdf",
      "section": "Totals",
      "page": 2,
      "chunkPreview": "Total HT : 12 480,00 EUR ...",
      "similarity": 0.83,
      "chunkId": "ck_8f1d2c40",
      "collectionId": null,
      "dossierName": null
    }
  ],
  "toolCalls": []
}
```

**400**: `message` missing or empty, or a block that does not match its schema (for example `maxTokens` above 4000).

**402**: The workspace has no questions left. Nothing was consumed.

```json
{ "error": "Insufficient tokens" }
```

**404**: `Agent not found: <id>`, or `Session not found: <sessionId>`.

```json
{ "error": "Agent not found: cm2k8x1ab0001qz0f7h3d9t4e" }
```

**500**: The model call failed, or the agent is a poste.

```json
{ "error": "Agent chat failed" }
```

#### Errors

| Status | Code | When |
| --- | --- | --- |
| 401 |  | Missing, invalid, revoked or expired key. |
| 402 | `API_KEY_BUDGET_EXCEEDED` | The key reached its own monthly question ceiling. |
| 403 | `scope_denied` | A restricted key without the `agents` scope. |
| 403 | `read_only_key` | The key carries the `readonly` scope. |
| 429 | `rate_limited` | More than 30 calls per minute on this route, or the key's own per-minute limit. Honour `retry-after`. |

#### Notes

The plain response carries the fields listed above. The streamed form ends with a `done` event holding the complete result of the turn, which also includes `proofId`, `consultations`, `mood`, `masked` and `contextUsed`; use it when you need those.

### POST /v1/agents/:id/chat/stream

Send a message to one agent and stream the answer.

Same request body and same turn as `POST /v1/agents/:id/chat`, answered as Server-Sent Events (`text/event-stream`). You receive tokens as they are produced, plus progress events for retrieval, tool calls and consultations, and a final `done` event with the complete result. See the [event reference](#stream-events) and the [client examples](#complete-streaming-client).

The HTTP status is 200 as soon as the stream opens. Failures that happen after that point, including an unknown agent or session and an exhausted allowance, arrive as an `error` event, not as a 4xx. Only an empty `message` or a malformed body is rejected before the stream starts.

- **Authentication:** API key (Bearer or x-api-key)
- **Scopes:** `agents`
- **Streaming:** yes, Server-Sent Events when `stream: true`

#### Path parameters

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | yes |  | The agent id. |

#### Request body

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `message` | `string` | yes |  | What you say to the agent. Must be a non-empty string. |
| `sessionId` | `string` | no |  | Continue an existing session ([Sessions](https://dev.subsidia.protypa.fr/docs/sessions.md)). Omit it to start a new one: its id comes back in the response. A session that does not exist, or that was started by another user, is a 404 `Session not found`. |
| `context` | `object[]` | no |  | Structured data injected into the agent prompt for this call only (it is not stored). See [Context injection](#context-injection). |
| `context.category` | `string` | yes |  | What kind of data this is, for example `client`, `invoice`, `crm`. Used as the block heading when `label` is absent. |
| `context.label` | `string` | no |  | Heading shown to the model instead of the category. |
| `context.data` | `object | string` | yes |  | The data. Objects are serialised to indented JSON. |
| `context.priority` | `"high" | "medium" | "low"` | no | `medium` | Blocks are ordered high first; `high` blocks are flagged as high priority to the model. One of: `high`, `medium`, `low`. |
| `extraction` | `object` | no |  | Extract structured facts from your message, alongside the answer. See [Fact extraction](#fact-extraction). |
| `extraction.categories` | `string[]` | yes |  | Categories to extract, at least one, for example `["company_info", "decisions", "preferences"]`. |
| `extraction.persist` | `boolean` | no | `true` | Store the extracted facts. Set `false` to only return them. |
| `options` | `object` | no |  | Generation settings. |
| `options.reflection` | `boolean` | no | `false` | Run a separate reasoning pass before the answer and return it as `thoughts`. Skipped for small talk. Adds latency and cost. |
| `options.temperature` | `number` | no | `0.8` | Sampling temperature, 0 to 2. |
| `options.maxTokens` | `number` | no | `1500` | Output ceiling, 100 to 4000. |
| `responseFormat` | `object` | no |  | Force a structured JSON answer. See [Structured JSON answers](#structured-json-answers). |
| `responseFormat.type` | `"json" | "text"` | yes |  | `json` forces a JSON object; `text` is the default prose mode. One of: `json`, `text`. |
| `responseFormat.fields` | `object` | no |  | Map of field name to a description of what it must contain. |
| `responseFormat.schema` | `string` | no |  | A free-form schema description, as an alternative to `fields`. |
| `knowledge` | `object` | no |  | Retrieval from the knowledge sources linked to the agent. On by default. |
| `knowledge.enabled` | `boolean` | no | `true` | Set `false` to answer without reading any document. |
| `knowledge.topK` | `number` | no | `5` | Passages to retrieve, 1 to 20. Questions that ask for a list (who are our clients) use 8 when you do not set it. |
| `tools` | `object` | no |  | The HTTP tools linked to the agent ([Tools](https://dev.subsidia.protypa.fr/docs/tools.md)). On by default. |
| `tools.enabled` | `boolean` | no | `true` | Set `false` so the agent never calls a tool in this turn. |
| `tools.toolIds` | `string[]` | no |  | Restrict the turn to a subset of the linked tools. Omit to allow all of them. Inactive tools are never offered. |
| `consult` | `object` | no |  | Let the agent ask other agents of the workspace for a second opinion mid-turn. See [Consulting colleagues](#consulting-colleagues). |
| `consult.enabled` | `boolean` | no | `true` | Set `false` for an isolated call. |

#### Request examples

_curl_

```bash
curl -N https://api.subsidia.protypa.fr/v1/agents/$AGENT_ID/chat/stream \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "What is the Q3 invoice total?"}'
```

#### Responses

**200**: A `text/event-stream`: one `data:` line per event, each holding a JSON object `{ "type": ..., "data": ... }`, blank-line separated. Comment lines `: ping` are sent every 15 seconds to keep proxies from closing an idle connection.

```text
data: {"type":"stage","data":{"name":"prepare","phase":"start"}}

data: {"type":"stage","data":{"name":"retrieval","phase":"start"}}

data: {"type":"stage","data":{"name":"retrieval","phase":"end","ms":412}}

data: {"type":"knowledge","data":[{"ref":1,"sourceId":"cm2k9aa7e0003qz0fh8w1p2lb","sourceName":"facture-q3-2026.pdf","section":"Totals","page":2,"chunkPreview":"Total HT : 12 480,00 EUR ...","similarity":0.83,"chunkId":"ck_8f1d2c40"}]}

data: {"type":"tool_call","data":{"toolName":"crm_lookup","arguments":{"client":"Atelier Moreau"}}}

data: {"type":"tool_result","data":{"toolName":"crm_lookup","arguments":{"client":"Atelier Moreau"},"result":{"status":"active"},"success":true,"durationMs":182}}

data: {"type":"token","data":{"delta":"The Q3 invoice is "}}

data: {"type":"token","data":{"delta":"12 480 EUR excluding VAT [1]."}}

: ping

data: {"type":"mood","data":{"agentId":"cm2k8x1ab0001qz0f7h3d9t4e","name":"Claire","emotion":"focus","valence":0.2,"arousal":0.4,"intensity":0.5}}

data: {"type":"done","data":{"agentId":"cm2k8x1ab0001qz0f7h3d9t4e","agentName":"Claire","agentRole":"Contrôle de gestion","content":"The Q3 invoice is 12 480 EUR excluding VAT [1].","sessionId":"cm2kc4t9p000bqz0fr6s2a8dk","memoriesSaved":[],"extracted":[],"tokenUsage":{"promptTokens":2140,"completionTokens":14,"totalTokens":2154},"knowledgeUsed":[{"ref":1,"sourceName":"facture-q3-2026.pdf"}],"toolCalls":[{"toolName":"crm_lookup","success":true,"durationMs":182}],"proofId":"pf_a059713a5f1c4e0b8d7a3c21e9b64f10"}}
```

**400**: `message` missing or empty.

```json
{ "error": "message is required" }
```

#### Errors

| Status | Code | When |
| --- | --- | --- |
| 401 |  | Missing, invalid, revoked or expired key. |
| 403 | `scope_denied` | A restricted key without the `agents` scope. |
| 429 | `rate_limited` | More than 30 calls per minute on this route, or the key's own limit. |

### POST /v1/agents/chat

Let Subsidia pick the best-fit agent, then answer.

Like `POST /v1/agents/:id/chat` without the agent. A lightweight planning step reads the message together with the name, role and mission of every agent of the workspace and routes to the single best fit; if it cannot decide, the oldest agent of the workspace answers. The response tells you who answered through `agentId`, `agentName` and `agentRole`. Keep using the returned `sessionId` and you stay in the same conversation, but note that the router re-evaluates every message: a follow-up may be routed to a different agent. For a stable conversation partner, take the `agentId` from the first answer and switch to the `:id` form.

The quality of the routing is the quality of your `role` and `mission` fields ([Agents](https://dev.subsidia.protypa.fr/docs/agents.md#design-tips)).

- **Authentication:** API key (Bearer or x-api-key)
- **Scopes:** `agents`

#### Request body

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `message` | `string` | yes |  | What you say to the agent. Must be a non-empty string. |
| `sessionId` | `string` | no |  | Continue an existing session ([Sessions](https://dev.subsidia.protypa.fr/docs/sessions.md)). Omit it to start a new one: its id comes back in the response. A session that does not exist, or that was started by another user, is a 404 `Session not found`. |
| `context` | `object[]` | no |  | Structured data injected into the agent prompt for this call only (it is not stored). See [Context injection](#context-injection). |
| `context.category` | `string` | yes |  | What kind of data this is, for example `client`, `invoice`, `crm`. Used as the block heading when `label` is absent. |
| `context.label` | `string` | no |  | Heading shown to the model instead of the category. |
| `context.data` | `object | string` | yes |  | The data. Objects are serialised to indented JSON. |
| `context.priority` | `"high" | "medium" | "low"` | no | `medium` | Blocks are ordered high first; `high` blocks are flagged as high priority to the model. One of: `high`, `medium`, `low`. |
| `extraction` | `object` | no |  | Extract structured facts from your message, alongside the answer. See [Fact extraction](#fact-extraction). |
| `extraction.categories` | `string[]` | yes |  | Categories to extract, at least one, for example `["company_info", "decisions", "preferences"]`. |
| `extraction.persist` | `boolean` | no | `true` | Store the extracted facts. Set `false` to only return them. |
| `options` | `object` | no |  | Generation settings. |
| `options.reflection` | `boolean` | no | `false` | Run a separate reasoning pass before the answer and return it as `thoughts`. Skipped for small talk. Adds latency and cost. |
| `options.temperature` | `number` | no | `0.8` | Sampling temperature, 0 to 2. |
| `options.maxTokens` | `number` | no | `1500` | Output ceiling, 100 to 4000. |
| `responseFormat` | `object` | no |  | Force a structured JSON answer. See [Structured JSON answers](#structured-json-answers). |
| `responseFormat.type` | `"json" | "text"` | yes |  | `json` forces a JSON object; `text` is the default prose mode. One of: `json`, `text`. |
| `responseFormat.fields` | `object` | no |  | Map of field name to a description of what it must contain. |
| `responseFormat.schema` | `string` | no |  | A free-form schema description, as an alternative to `fields`. |
| `knowledge` | `object` | no |  | Retrieval from the knowledge sources linked to the agent. On by default. |
| `knowledge.enabled` | `boolean` | no | `true` | Set `false` to answer without reading any document. |
| `knowledge.topK` | `number` | no | `5` | Passages to retrieve, 1 to 20. Questions that ask for a list (who are our clients) use 8 when you do not set it. |
| `tools` | `object` | no |  | The HTTP tools linked to the agent ([Tools](https://dev.subsidia.protypa.fr/docs/tools.md)). On by default. |
| `tools.enabled` | `boolean` | no | `true` | Set `false` so the agent never calls a tool in this turn. |
| `tools.toolIds` | `string[]` | no |  | Restrict the turn to a subset of the linked tools. Omit to allow all of them. Inactive tools are never offered. |
| `consult` | `object` | no |  | Let the agent ask other agents of the workspace for a second opinion mid-turn. See [Consulting colleagues](#consulting-colleagues). |
| `consult.enabled` | `boolean` | no | `true` | Set `false` for an isolated call. |

#### Request examples

_curl_

```bash
curl https://api.subsidia.protypa.fr/v1/agents/chat \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "Can you check the VAT on the attached invoice?"}'
```

_TypeScript_

```typescript
const res = await fetch('https://api.subsidia.protypa.fr/v1/agents/chat', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.SUBSIDIA_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ message: 'Can you check the VAT on the attached invoice?' }),
})
const turn = await res.json()
console.log('Answered by', turn.agentName, '(' + turn.agentRole + ')')
console.log(turn.content)
```

_Python_

```python
import os, requests

turn = requests.post(
    "https://api.subsidia.protypa.fr/v1/agents/chat",
    headers={"Authorization": "Bearer " + os.environ["SUBSIDIA_API_KEY"]},
    json={"message": "Can you check the VAT on the attached invoice?"},
    timeout=300,
).json()
print("Answered by", turn["agentName"], "-", turn["agentRole"])
print(turn["content"])
```

#### Responses

**200**: Same shape as the single-agent response. `agentId` is the agent that was picked.

```json
{
  "agentId": "cm2k8x1ab0001qz0f7h3d9t4e",
  "agentName": "Claire",
  "agentRole": "Contrôle de gestion",
  "content": "The Q3 invoice is 12 480 EUR excluding VAT [1]. It matches the purchase order, but the delivery note is missing from the file.",
  "sessionId": "cm2kc4t9p000bqz0fr6s2a8dk",
  "memoriesSaved": [
    { "key": "q3_invoice_total", "value": "Q3 invoice: 12 480 EUR excl. VAT.", "type": "fact", "provenance": "knowledge", "salience": 0.65 }
  ],
  "extracted": [],
  "tokenUsage": { "promptTokens": 2140, "completionTokens": 62, "totalTokens": 2202 },
  "knowledgeUsed": [
    {
      "ref": 1,
      "sourceId": "cm2k9aa7e0003qz0fh8w1p2lb",
      "sourceName": "facture-q3-2026.pdf",
      "section": "Totals",
      "page": 2,
      "chunkPreview": "Total HT : 12 480,00 EUR ...",
      "similarity": 0.83,
      "chunkId": "ck_8f1d2c40",
      "collectionId": null,
      "dossierName": null
    }
  ],
  "toolCalls": []
}
```

**400**: `message` missing or empty.

**402**: The workspace has no questions left.

```json
{ "error": "Insufficient tokens" }
```

**404**: The workspace has no agents (`No agents available in this workspace`), or the `sessionId` is unknown.

```json
{ "error": "No agents available in this workspace" }
```

**500**: Model failure, or the picked agent is a poste.

#### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 |  | No agent in the workspace, or unknown session. |
| 429 | `rate_limited` | More than 30 calls per minute on this route. |

### POST /v1/agents/chat/stream

Let Subsidia pick the best-fit agent, then stream the answer.

The streamed form of auto-pick. The agent is chosen **before** the stream opens, so the two failures that depend on it (no agent in the workspace, or the planning step failing) are ordinary JSON errors (404 and 500); after that, events are the same as in `POST /v1/agents/:id/chat/stream`. The `done` event tells you which agent answered (`agentId`, `agentName`).

- **Authentication:** API key (Bearer or x-api-key)
- **Scopes:** `agents`
- **Streaming:** yes, Server-Sent Events when `stream: true`

#### Request body

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `message` | `string` | yes |  | What you say to the agent. Must be a non-empty string. |
| `sessionId` | `string` | no |  | Continue an existing session ([Sessions](https://dev.subsidia.protypa.fr/docs/sessions.md)). Omit it to start a new one: its id comes back in the response. A session that does not exist, or that was started by another user, is a 404 `Session not found`. |
| `context` | `object[]` | no |  | Structured data injected into the agent prompt for this call only (it is not stored). See [Context injection](#context-injection). |
| `context.category` | `string` | yes |  | What kind of data this is, for example `client`, `invoice`, `crm`. Used as the block heading when `label` is absent. |
| `context.label` | `string` | no |  | Heading shown to the model instead of the category. |
| `context.data` | `object | string` | yes |  | The data. Objects are serialised to indented JSON. |
| `context.priority` | `"high" | "medium" | "low"` | no | `medium` | Blocks are ordered high first; `high` blocks are flagged as high priority to the model. One of: `high`, `medium`, `low`. |
| `extraction` | `object` | no |  | Extract structured facts from your message, alongside the answer. See [Fact extraction](#fact-extraction). |
| `extraction.categories` | `string[]` | yes |  | Categories to extract, at least one, for example `["company_info", "decisions", "preferences"]`. |
| `extraction.persist` | `boolean` | no | `true` | Store the extracted facts. Set `false` to only return them. |
| `options` | `object` | no |  | Generation settings. |
| `options.reflection` | `boolean` | no | `false` | Run a separate reasoning pass before the answer and return it as `thoughts`. Skipped for small talk. Adds latency and cost. |
| `options.temperature` | `number` | no | `0.8` | Sampling temperature, 0 to 2. |
| `options.maxTokens` | `number` | no | `1500` | Output ceiling, 100 to 4000. |
| `responseFormat` | `object` | no |  | Force a structured JSON answer. See [Structured JSON answers](#structured-json-answers). |
| `responseFormat.type` | `"json" | "text"` | yes |  | `json` forces a JSON object; `text` is the default prose mode. One of: `json`, `text`. |
| `responseFormat.fields` | `object` | no |  | Map of field name to a description of what it must contain. |
| `responseFormat.schema` | `string` | no |  | A free-form schema description, as an alternative to `fields`. |
| `knowledge` | `object` | no |  | Retrieval from the knowledge sources linked to the agent. On by default. |
| `knowledge.enabled` | `boolean` | no | `true` | Set `false` to answer without reading any document. |
| `knowledge.topK` | `number` | no | `5` | Passages to retrieve, 1 to 20. Questions that ask for a list (who are our clients) use 8 when you do not set it. |
| `tools` | `object` | no |  | The HTTP tools linked to the agent ([Tools](https://dev.subsidia.protypa.fr/docs/tools.md)). On by default. |
| `tools.enabled` | `boolean` | no | `true` | Set `false` so the agent never calls a tool in this turn. |
| `tools.toolIds` | `string[]` | no |  | Restrict the turn to a subset of the linked tools. Omit to allow all of them. Inactive tools are never offered. |
| `consult` | `object` | no |  | Let the agent ask other agents of the workspace for a second opinion mid-turn. See [Consulting colleagues](#consulting-colleagues). |
| `consult.enabled` | `boolean` | no | `true` | Set `false` for an isolated call. |

#### Request examples

_curl_

```bash
curl -N https://api.subsidia.protypa.fr/v1/agents/chat/stream \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "Can you check the VAT on the attached invoice?"}'
```

#### Responses

**200**: A `text/event-stream`, identical in format to the single-agent stream.

```text
data: {"type":"stage","data":{"name":"prepare","phase":"start"}}

data: {"type":"stage","data":{"name":"retrieval","phase":"start"}}

data: {"type":"stage","data":{"name":"retrieval","phase":"end","ms":412}}

data: {"type":"knowledge","data":[{"ref":1,"sourceId":"cm2k9aa7e0003qz0fh8w1p2lb","sourceName":"facture-q3-2026.pdf","section":"Totals","page":2,"chunkPreview":"Total HT : 12 480,00 EUR ...","similarity":0.83,"chunkId":"ck_8f1d2c40"}]}

data: {"type":"tool_call","data":{"toolName":"crm_lookup","arguments":{"client":"Atelier Moreau"}}}

data: {"type":"tool_result","data":{"toolName":"crm_lookup","arguments":{"client":"Atelier Moreau"},"result":{"status":"active"},"success":true,"durationMs":182}}

data: {"type":"token","data":{"delta":"The Q3 invoice is "}}

data: {"type":"token","data":{"delta":"12 480 EUR excluding VAT [1]."}}

: ping

data: {"type":"mood","data":{"agentId":"cm2k8x1ab0001qz0f7h3d9t4e","name":"Claire","emotion":"focus","valence":0.2,"arousal":0.4,"intensity":0.5}}

data: {"type":"done","data":{"agentId":"cm2k8x1ab0001qz0f7h3d9t4e","agentName":"Claire","agentRole":"Contrôle de gestion","content":"The Q3 invoice is 12 480 EUR excluding VAT [1].","sessionId":"cm2kc4t9p000bqz0fr6s2a8dk","memoriesSaved":[],"extracted":[],"tokenUsage":{"promptTokens":2140,"completionTokens":14,"totalTokens":2154},"knowledgeUsed":[{"ref":1,"sourceName":"facture-q3-2026.pdf"}],"toolCalls":[{"toolName":"crm_lookup","success":true,"durationMs":182}],"proofId":"pf_a059713a5f1c4e0b8d7a3c21e9b64f10"}}
```

**400**: `message` missing or empty.

**404**: The workspace has no agents.

```json
{ "error": "No agents available in this workspace" }
```

**500**: The agent selection step failed.

```json
{ "error": "Agent selection failed" }
```

#### Errors

| Status | Code | When |
| --- | --- | --- |
| 429 | `rate_limited` | More than 30 calls per minute on this route. |

## Stream events

Every event is one line `data: <json>` followed by a blank line, where the JSON is `{ "type": "<name>", "data": <payload> }`. A line starting with a colon (`: ping`) is a keep-alive comment, not an event: ignore it. The stream ends after `done` or `error`; the server then closes the connection, there is no `[DONE]` sentinel.

| type | Payload (`data`) | When | What to do |
| --- | --- | --- | --- |
| `stage` | `{ name, phase, ms?, detail? }`. `name` is one of `prepare`, `retrieval`, `plan`, `websearch`, `reflection`, `answer`; `phase` is `start` or `end`; `ms` is the duration on `end`. | Around each internal step. | Drive a progress indicator ("searching the documents..."). Safe to ignore. |
| `knowledge` | Array of passages: `{ ref, sourceId, sourceName, section, page, chunkPreview, similarity, chunkId, origin?, url?, collectionId, dossierName }`. | Once, before the answer, when retrieval found passages. | Keep the list: `[n]` markers in the answer point to `ref`. Show sources as soon as they exist. |
| `thought` | `{ delta }` | Only with `options.reflection`, while the agent reasons. | Render in a collapsible "reasoning" area. Never mix with the answer. |
| `tool_call` | `{ toolName, arguments }` | The agent decided to call one of your tools. | Show "calling crm_lookup...". The call is executed by Subsidia, not by you. |
| `tool_result` | `{ toolName, arguments, result?, success, error?, durationMs }` | The tool call finished. | Log it. `success: false` means your endpoint failed; the agent is told and answers anyway. |
| `consult_start` | `{ colleagueName, question }` | The agent asks a colleague for an opinion. | Show who is being asked what. A consultation the user cannot read would be a black box. |
| `consult_result` | `{ colleagueId, colleagueName, colleagueRole, question, answer, durationMs, error? }` | The colleague answered. | Display it as a quoted aside; the final answer builds on it. |
| `token` | `{ delta }` | Repeatedly, as the answer is generated. | Append `delta` to the visible answer. With `responseFormat.type: "json"` the deltas are raw JSON text; parse the final `contentParsed` instead. |
| `mood` | `{ agentId, name, emotion, valence, arousal, intensity, dominance?, narrative? }` | After the answer, text mode. | Optional. The agent's emotional state after the exchange. |
| `done` | The complete result of the turn: `agentId`, `agentName`, `agentRole`, `content`, `contentParsed?`, `thoughts?`, `sessionId`, `memoriesSaved`, `extracted`, `tokenUsage`, `knowledgeUsed?`, `toolCalls?`, `consultations?`, `contextUsed?`, `masked?`, `mood?`, `proofId?`. | Last event on success. The exchange is saved and the proof certificate is signed before it is sent. | Replace the streamed text by `content` (it is the canonical, final text), store `sessionId`, keep `proofId` next to your record. |
| `error` | `{ message }` | Any failure after the stream opened. | Show the message and stop reading. |

> **WARNING: Handle error as a normal event**
> Because the stream is already open, an unknown agent, an unknown session, an exhausted allowance and a model outage are all delivered as `{ "type": "error" }` with HTTP status 200. A client that only checks the status code will treat a failed turn as a success and display nothing.

> **INFO: Streamed equals saved**
> The text you streamed is the text that is saved and signed. Internal markers (the memory blocks, mood tags, and citation markers that point to no passage) are filtered out of the deltas on the way, so `content` in `done` matches what the user saw. The proof certificate in `proofId` can be fetched with [`GET /v1/proofs/:id`](https://dev.subsidia.protypa.fr/docs/proofs.md) using a key that carries the `engine` scope.

## Complete streaming client

A client has to do four things: read the body incrementally, split it on blank lines, parse `data:` lines as JSON, and treat `error` as a failure. The examples below print tokens as they arrive, collect citations and tool calls, and return the final `done` result.

**Streaming client**

_TypeScript_

```typescript
type StreamEvent =
  | { type: 'stage'; data: { name: string; phase: 'start' | 'end'; ms?: number } }
  | { type: 'knowledge'; data: Array<{ ref: number; sourceName: string; page: number | null }> }
  | { type: 'thought' | 'token'; data: { delta: string } }
  | { type: 'tool_call'; data: { toolName: string; arguments: Record<string, unknown> } }
  | { type: 'tool_result'; data: { toolName: string; success: boolean; durationMs: number; error?: string } }
  | { type: 'consult_start'; data: { colleagueName: string; question: string } }
  | { type: 'consult_result'; data: { colleagueName: string; answer: string } }
  | { type: 'mood'; data: Record<string, unknown> }
  | { type: 'done'; data: { content: string; sessionId: string; proofId?: string; knowledgeUsed?: unknown[] } }
  | { type: 'error'; data: { message: string } }

export async function streamAgent(
  agentId: string,
  message: string,
  sessionId?: string,
  onToken: (delta: string) => void = (d) => process.stdout.write(d),
) {
  const res = await fetch('https://api.subsidia.protypa.fr/v1/agents/' + agentId + '/chat/stream', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.SUBSIDIA_API_KEY,
      'Content-Type': 'application/json',
      Accept: 'text/event-stream',
    },
    body: JSON.stringify({ message, sessionId }),
  })
  // Errors before the stream opens (400, 401, 403, 429) are plain JSON.
  if (!res.ok || !res.body) throw new Error(res.status + ' ' + (await res.text()))

  const reader = res.body.getReader()
  const decoder = new TextDecoder()
  let buffer = ''
  let done: Extract<StreamEvent, { type: 'done' }>['data'] | undefined

  while (true) {
    const { value, done: finished } = await reader.read()
    if (finished) break
    buffer += decoder.decode(value, { stream: true })

    let boundary: number
    while ((boundary = buffer.indexOf('\n\n')) !== -1) {
      const raw = buffer.slice(0, boundary)
      buffer = buffer.slice(boundary + 2)
      const line = raw.split('\n').find((l) => l.startsWith('data: '))
      if (!line) continue // ": ping" keep-alive comment
      const event = JSON.parse(line.slice(6)) as StreamEvent

      switch (event.type) {
        case 'token':
          onToken(event.data.delta)
          break
        case 'tool_call':
          console.error('\n[tool] calling ' + event.data.toolName)
          break
        case 'tool_result':
          console.error('[tool] ' + event.data.toolName + (event.data.success ? ' ok' : ' failed') + ' in ' + event.data.durationMs + ' ms')
          break
        case 'done':
          done = event.data
          break
        case 'error':
          throw new Error(event.data.message)
      }
    }
  }
  if (!done) throw new Error('Stream ended without a done event')
  return done // done.content is the canonical text; keep done.sessionId and done.proofId
}

const turn = await streamAgent(process.env.AGENT_ID!, 'What is the Q3 invoice total?')
console.log('\n\nsession', turn.sessionId, 'proof', turn.proofId)
```

_Python_

```python
import json, os, sys
import requests


def stream_agent(agent_id, message, session_id=None):
    res = requests.post(
        "https://api.subsidia.protypa.fr/v1/agents/" + agent_id + "/chat/stream",
        headers={
            "Authorization": "Bearer " + os.environ["SUBSIDIA_API_KEY"],
            "Accept": "text/event-stream",
        },
        json={"message": message, "sessionId": session_id},
        stream=True,
        timeout=(10, 300),
    )
    # Errors before the stream opens (400, 401, 403, 429) are plain JSON.
    if res.status_code != 200:
        raise RuntimeError(str(res.status_code) + " " + res.text)

    done = None
    for raw in res.iter_lines(decode_unicode=True):
        if not raw or not raw.startswith("data: "):
            continue  # blank separator or ": ping" keep-alive
        event = json.loads(raw[6:])
        kind, data = event["type"], event["data"]

        if kind == "token":
            sys.stdout.write(data["delta"])
            sys.stdout.flush()
        elif kind == "tool_call":
            print("\n[tool] calling", data["toolName"], file=sys.stderr)
        elif kind == "tool_result":
            status = "ok" if data["success"] else "failed"
            print("[tool]", data["toolName"], status, file=sys.stderr)
        elif kind == "done":
            done = data
        elif kind == "error":
            raise RuntimeError(data["message"])

    if done is None:
        raise RuntimeError("Stream ended without a done event")
    return done  # done["content"] is the canonical text


turn = stream_agent(os.environ["AGENT_ID"], "What is the Q3 invoice total?")
print("\n\nsession", turn["sessionId"], "proof", turn.get("proofId"))
```

## Context injection

`context` lets your application hand the agent live data without storing it anywhere: the customer record on screen, today's figures, the form the user is filling in. Each item becomes a labelled section of the agent's prompt, sorted `high` priority first. It is used for this call only and is not saved to the session.

The agent can cite what it used. Items it relied on come back in `contextUsed` (streamed `done` event) with `origin: "context"`, numbered after the document passages in `knowledgeUsed`, so a `[3]` in the answer may point to your data rather than to a document: do not offer to open it as a file. Provenance also applies to memory: a fact the agent saves from context is stored with provenance `data` ([Agent memory](https://dev.subsidia.protypa.fr/docs/agent-memory.md#provenance)).

When the call is served by an external model provider, personal data in the context is masked first and restored in the answer, like any other Subsidia call ([Synapse](https://dev.subsidia.protypa.fr/docs/synapse.md)).

```json
{
  "message": "Is this client eligible for the annual discount?",
  "context": [
    { "category": "client", "label": "Client record", "data": { "name": "Atelier Moreau", "since": "2023-04-01" }, "priority": "high" },
    { "category": "pricing_rules", "data": "Annual discount applies after 24 months of continuous subscription.", "priority": "medium" }
  ]
}
```

## Fact extraction

With `extraction`, a separate pass reads **your message** (not the answer) and pulls out structured facts in the categories you name. They come back in `extracted` as `{ category, key, value, confidence }` and, unless `persist` is `false`, are stored in the workspace's extracted data. It is a convenient way to turn free-text intake ("I'm Anne, I run a bakery in Lyon with 4 employees, I prefer email") into fields for your CRM in the same call that produces the reply.

_Request_

```json
{
  "message": "I'm Anne, I run a bakery in Lyon with 4 employees. Please email me, not call.",
  "extraction": { "categories": ["company_info", "preferences"], "persist": false }
}
```

_Response (extracted)_

```json
{
  "extracted": [
    { "category": "company_info", "key": "business_type", "value": "bakery", "confidence": 0.95 },
    { "category": "company_info", "key": "city", "value": "Lyon", "confidence": 0.93 },
    { "category": "company_info", "key": "employees", "value": "4", "confidence": 0.9 },
    { "category": "preferences", "key": "contact_channel", "value": "email", "confidence": 0.92 }
  ]
}
```

## Structured JSON answers

Set `responseFormat.type` to `json` and describe what you want in `fields` (a map of field name to description) or as a free-form `schema`. The agent answers with a JSON object only. In the response, `content` is the JSON text and `contentParsed` is the parsed object; if the model's output was not valid JSON, `contentParsed` is absent and you should treat `content` as unparsed.

JSON mode changes how the turn behaves: the agent does not write memories, does not consult colleagues, and with `options.reflection` its reasoning is returned as a `thoughts` field inside the JSON instead of a separate pass. Validate the object on your side; the format is an instruction to the model, not a schema enforced by the server.

_Request_

```json
{
  "message": "Is the delivery note in the file for invoice Q3-114?",
  "responseFormat": {
    "type": "json",
    "fields": {
      "answer": "direct answer, yes or no",
      "evidence": "the document and page that support the answer, or null",
      "confidence": "a number between 0 and 1"
    }
  }
}
```

_Response_

```json
{
  "agentName": "Claire",
  "content": "{\"answer\":\"no\",\"evidence\":null,\"confidence\":0.8}",
  "contentParsed": { "answer": "no", "evidence": null, "confidence": 0.8 },
  "sessionId": "cm2kc4t9p000bqz0fr6s2a8dk"
}
```

## Knowledge and citations

When retrieval is on, the agent reads passages from the knowledge sources **linked to it** (attach them with the `agentIds` field of [`POST /v1/knowledge-sources`](https://dev.subsidia.protypa.fr/docs/knowledge-sources.md)), within the access rules of the workspace. The retrieved passages are numbered `1..n` in `knowledgeUsed`. In the answer, a claim backed by a passage is followed by its marker, for example `[1]`; markers that match no passage are removed before you see them.

Subsidia also marks provenance inside the text, and computes it itself instead of trusting the model: every marker the model wrote is removed, then each anchor an auditor would copy (a number, a date, an amount, a proper name) is checked against the retrieved passages. An anchor found in a passage that discusses the same subject is wrapped as U+27E6 anchor U+27E7 followed by its `[n]` (white square brackets, a verified claim); an anchor found nowhere is wrapped in U+27EC and U+27ED (white tortoise-shell brackets) and is the agent's own, **not** backed by your documents. Reasoning and opinion stay unmarked. If you display answers, either render these spans (a rail or underline is enough) or strip the delimiters; do not leave them in as plain text. A missing document is a reason to mark provenance, not to refuse, so the agent still answers when the base is silent.

The passages retrieved for a turn are committed to the proof certificate of the turn, so an auditor can check what the answer was grounded in ([Proof certificates](https://dev.subsidia.protypa.fr/docs/proofs.md)).

## Consulting colleagues

With `consult` on (the default), an agent can ask another agent of the workspace for a second opinion before it answers, through an internal tool called `ask_colleague`. Rules that matter for an integration:

- It is the colleague's **judgment**, inside its declared mission, that is consulted. The colleague answers from its persona and expertise alone, with no documents and no tools of its own, in at most about 400 tokens, and consultations never nest.
- At most **two consultations per turn**, among up to twelve other agents of the workspace. Postes are not colleagues.
- Consultations are visible: they stream as `consult_start` and `consult_result` events and come back in `consultations` in the `done` event, each with the question, the answer and the duration. Nothing happens off the record.
- It is disabled in JSON mode, and with `consult: { "enabled": false }`.

Write each agent's `mission` and `outOfScope` so that colleagues know what to ask whom ([Agents](https://dev.subsidia.protypa.fr/docs/agents.md#design-tips)).

## Tool calls

If the agent has [tools](https://dev.subsidia.protypa.fr/docs/tools.md) linked and `tools.enabled` is not `false`, the model may ask for one or more of them while preparing its answer. Subsidia calls your endpoint, reads the JSON you return, feeds it back to the model, and repeats for at most three rounds before the final answer. Each call is reported in `toolCalls` (`toolName`, `arguments`, `result`, `success`, `error`, `durationMs`) and streamed as `tool_call` and `tool_result`. A failing tool does not fail the turn: the agent receives the error and answers with what it has, and it is told never to claim an action it has no tool result for. Use `tools.toolIds` to narrow a turn to the tools relevant to the screen you are on.

## Memory and sessions

Each turn is saved in its session ([Sessions](https://dev.subsidia.protypa.fr/docs/sessions.md)): your message and the agent's answer, with its metadata (sources, tool calls, tokens). The agent also recalls and may write long-term memory ([Agent memory](https://dev.subsidia.protypa.fr/docs/agent-memory.md)); the notes written during the turn are returned in `memoriesSaved`. To call an agent without memory, use the Gateway address `agent:<slug>+nomemory`.

## Frequently asked

**Which is faster, streaming or not?**

Total time is the same. Streaming shows the first words after retrieval and any tool calls complete, which is what users perceive. On a small local machine a long turn can take minutes; Subsidia does not time out a local model, so set a generous timeout on your client (300 seconds or more) rather than retrying, because a retry starts a second turn.

**Can I use the OpenAI SDK instead?**

Yes. Call [`/v1/chat/completions`](https://dev.subsidia.protypa.fr/docs/gateway.md) with `model: "agent:<slug>"`. You get the same agent with its memory, documents and tools, a standard streaming protocol, and a proof id in the `x-pulse-proof` header. Use the endpoints on this page when you need citations, `context`, `extraction`, `responseFormat` or the event reference above.

**Why is the answer different each time?**

Temperature defaults to 0.8 for personas, which keeps them in character. Lower `options.temperature` (0 to 0.3) for factual, repeatable answers.

**How do I keep a conversation going across my own users?**

Store one `sessionId` per end user in your database. A session can only be continued by the user who created it (here, the owner of the API key), so one key serves your whole application and you map sessions to your users yourself.
