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

# Chat

> Send a message to a tenant's public chatbot.

The public chat endpoint is the only public agent surface on Animam. Browsers (via the widget) and external AI agents both go through it. The chatbot owns all tools — `EXPLORE_CORPUS`, `RECOMMEND_PRODUCT`, `SUBMIT_FORM`, `BOOK_MEETING`, `COLLECT_PAYMENT`, etc. — and orchestrates them on the caller's behalf. There is no separate `tools/list` endpoint exposed to anonymous callers.

## POST /chat/{slug}

```
POST https://api.animam.ai/chat/{slug}
```

**Auth**: none required. Origin/widget-key gates apply if the tenant has them configured. Plan must include the `widget` capability.

### Request body

| Field            | Type                | Required | Description                                                                                |
| ---------------- | ------------------- | -------- | ------------------------------------------------------------------------------------------ |
| `message`        | string (≤ 15000)    | Yes      | The user (or agent) message                                                                |
| `sessionId`      | string (≤ 100)      | Yes      | Session handle. Reuse to keep multi-turn context                                           |
| `segment`        | string              | No       | Segment slug, defaults to the tenant's default segment                                     |
| `stream`         | boolean             | No       | `true` → SSE. Defaults: `true` for human visitors, `false` when `X-Agent-Model` is present |
| `pageUrl`        | string              | No       | Optional page URL where the conversation happens                                           |
| `pageContext`    | object              | No       | `{ title, url, ... }` — optional page metadata                                             |
| `visitorId`      | string              | No       | Persistent visitor handle (alternative to `X-Visitor-Id` header)                           |
| `visitorToken`   | string              | No       | JWT signed by tenant's `visitorSigningKey` (Mode A auth)                                   |
| `turnstileToken` | string              | No       | Required if the tenant has Turnstile enabled                                               |
| `locale`         | string (`en`, `fr`) | No       | Locale hint (alternative to `X-Animam-Locale` header)                                      |

### Headers

| Header                                            | Purpose                                                                                 |
| ------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `X-Agent-Model`                                   | Declare the calling agent's model (e.g. `claude-opus-4-7`). Triggers the agent envelope |
| `X-API-Key` / `X-LLM-Key`                         | Optional BYOK passthrough — call the tenant's chatbot with your own LLM key             |
| `X-LLM-Provider`, `X-LLM-Model`, `X-LLM-Base-Url` | BYOK provider config                                                                    |
| `X-Visitor-Token`, `X-Visitor-Id`                 | Visitor identity propagation                                                            |
| `X-Animam-Admin-Token`                            | (WordPress) admin JWT for capability-scoped MCP tool access                             |
| `X-Animam-Locale`                                 | Locale hint (`en`, `fr`, …)                                                             |

### Response — JSON envelope (default for agents)

When `X-Agent-Model` is present and `stream` is not explicitly set, the API returns a single JSON document.

```json theme={null}
{
  "message": "We're open Monday to Friday, 9am to 6pm.",
  "sessionId": "agent-session-abc",
  "conversationId": "d67e62ae-7b5c-405f-9bec-f1352d3de988",
  "messageCount": 2,
  "visitorType": "agent",
  "agentModel": "claude-opus-4-7",
  "toolExecutions": [
    {
      "name": "explore_corpus",
      "type": "EXPLORE_CORPUS",
      "success": true,
      "summary": "Found 1 entry: business-hours",
      "data": { "entries": [...] },
      "timestamp": "2026-05-05T14:32:11.420Z"
    }
  ],
  "contactSubmitted": false,
  "toolResults": { "explore_corpus": true },
  "llmSource": "managed",
  "llmProvider": "anthropic",
  "llmModel": "claude-haiku-4-5"
}
```

| Field              | Description                                                                                                                                                |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `message`          | Prose reply from the chatbot                                                                                                                               |
| `conversationId`   | DB conversation handle                                                                                                                                     |
| `visitorType`      | `"agent"` if `X-Agent-Model` or `[AGENT:]` prefix detected, else `"human"`                                                                                 |
| `agentModel`       | Declarative echo of what the caller said. Truncated to 100 chars. Not cryptographically verified                                                           |
| `toolExecutions`   | Tools the chatbot invoked — sanitized: name, type, success flag, one-line summary, structured data, timestamp. Raw inputs intentionally omitted (PII risk) |
| `contactSubmitted` | `true` if a `SUBMIT_FORM` tool succeeded — useful for lead attribution                                                                                     |
| `llmModel`         | Exact model that generated the reply (e.g. `claude-haiku-4-5`). Absent when `llmSource: "webhook"`                                                         |
| `status`           | `"escalated"` when the chatbot triggered `ESCALATE_TO_HUMAN`                                                                                               |

The HTTP response also carries `X-AI-Generated: true` (AI Act Art. 50 transparency).

### Response — SSE stream (default for browsers)

When `stream: true` (the default for human visitors), the API returns Server-Sent Events.

```
data: {"text": "We're "}
data: {"text": "open "}
data: {"text": "Monday to Friday..."}
data: {"done": true, "sessionId": "...", "conversationId": "...", "messageCount": 2, "visitorType": "human", "llmSource": "managed", "llmProvider": "anthropic", "llmModel": "claude-haiku-4-5"}
```

The terminal `done` event carries all the envelope fields shown in the JSON shape (including `toolExecutions` when the caller is an agent).

### Errors

| HTTP | Code                                      | Body                                                  |
| ---- | ----------------------------------------- | ----------------------------------------------------- |
| 400  | —                                         | `{"error": "message and sessionId are required"}`     |
| 400  | —                                         | `{"error": "Message too long (max 15000 chars)"}`     |
| 403  | —                                         | `{"error": "Origin not allowed"}`                     |
| 403  | —                                         | `{"error": "Invalid or missing widget key"}`          |
| 403  | `TURNSTILE_REQUIRED` / `TURNSTILE_FAILED` | Turnstile gate                                        |
| 402  | `NOT_ACTIVATED`                           | Plan does not include `widget` capability             |
| 404  | —                                         | `{"error": "Tenant not found"}`                       |
| 429  | `LIMIT_REACHED`                           | `{"error": "...", "current", "limit", "nextResetAt"}` |

### Rate limits

Per-IP dual window: **20 messages / minute** + **200 messages / hour**. Exceeded calls return HTTP 429.

## Quick examples

### Agent-to-agent (Python)

```python theme={null}
import requests

r = requests.post(
    "https://api.animam.ai/chat/acme-corp",
    headers={"X-Agent-Model": "claude-opus-4-7", "Content-Type": "application/json"},
    json={"message": "What plans do you offer?", "sessionId": "sess-1"},
)
envelope = r.json()
print(envelope["message"])
print("Generated by:", envelope.get("llmModel"))
```

### Streaming (JavaScript widget)

```javascript theme={null}
const res = await fetch(`https://api.animam.ai/chat/${slug}`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ message: userInput, sessionId, stream: true })
})
const reader = res.body.getReader()
const decoder = new TextDecoder()
while (true) {
  const { done, value } = await reader.read()
  if (done) break
  for (const line of decoder.decode(value).split('\n')) {
    if (line.startsWith('data: ')) {
      const evt = JSON.parse(line.slice(6))
      if (evt.text) appendToChat(evt.text)
      if (evt.done) finalize(evt)
    }
  }
}
```

## See also

<CardGroup cols={2}>
  <Card title="Agent access guide" icon="robot" href="/for-llms/api-access">
    Full agent-to-agent walkthrough
  </Card>

  <Card title="Tools (concept)" icon="wrench" href="/guides/tools">
    The 13 tool types the chatbot can invoke
  </Card>
</CardGroup>
