# Agent: Bots
**Page:** api/agent/bots
[Download Raw Markdown](./api/agent/bots.md)
---
# Agent: Bots
A Bot is a long-lived assistant of the Hoody Agent. It works by opening delegate sessions on containers and agents, following them, and reporting back what they did. Create a Bot (or use the default Bot, `chief`, which is created on first use), post a message (the reply arrives on the stream and in the log), follow it, and stop a delegate when needed.
This page is not the Hoody Bot service at `/api/bot/`, which controls Hoody from a chat app. Bots here run on the agent.
A Bot lives in one realm: global (not tied to a realm, its delegates see every container of the account) or a realm id. Name the realm with `X-Hoody-Realm` or `?realm=`; omitted, the agent's current realm. `?realm=all` is accepted only on `GET /bots`; anywhere else it is 400 `bad_request`. An agent pinned to one realm refuses any other realm, and `all`, with 400 `realm_scope_unsupported`.
Guardrails are limits on the work, not instructions to the Bot. They apply to the Bot and to every delegate it opens.
The sessions a Bot opens are listed with [List a Bot's delegates](#list-a-bots-delegates) and read with the sessions routes (see [Sessions](/api/agent/sessions/)).
Every Bot also has a per-realm URL at `/api/v1/agent/bots/{realm}/{bot}`, ready to paste as the base URL of an OpenAI client (`/v1`), an Anthropic client (``), or an MCP client (`/mcp`). The compat paths under `/api/v1/agent/compat/openai/v1/...` and `/api/v1/agent/compat/anthropic/v1/...` reach the same Bots. Apps send any `Authorization` or `x-api-key` value; access is decided before the request reaches the agent.
---
## List Bots
### `GET /api/v1/agent/bots`
Lists the realm's Bots, sorted by id. The default Bot, `chief`, is created on first use, so the list is never empty. With `?realm=all` it lists the Bots of every realm this login serves, global included, sorted by realm and then id; each row carries its realm and address. Under `?realm=all`, `chief` is not created in a realm that has no Bots yet.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `realm` | query | string | No | The realm to list: `global` (Bots not tied to a realm), a 24-hex realm id, or `all` for every realm this login serves, global included. Also accepted as the `X-Hoody-Realm` header, except `all`. Omitted, the agent's current realm. |
| `page` | query | integer | No | 1-based page number for pagination. |
| `limit` | query | integer | No | Maximum items per page (0 = no pagination). |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `global` (not tied to a realm) or a 24-hex realm id (also accepted as `?realm=`). On a session route it names the realm the session is looked up in. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots?realm=global" \
-H "Authorization: Bearer "
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.agent.bots.listIterator({ realm: 'global' })) {
// ...
}
```
```json
{
"items": [
{
"id": "chief",
"uid": "0199c3a2-6f10-7c4e-9b2a-3d5e8f1a2b4c",
"realm": "global",
"address": "bot:global/chief",
"name": "Chief",
"role": "Keeps the web app's releases moving.",
"session_id": "5f0c2a91d3b44e7f",
"model": "",
"guardrails": "Never push to main.",
"allowed_containers": [],
"allowed_agents": [],
"yolo": false,
"yolo_unapplied": [],
"created": "2026-10-08T10:30:00Z",
"created_by": { "kind": "system" },
"autonomy": { "used": 0, "max": 10, "per_delegate_max": 3, "waiting_for_you": false },
"open_delegates": 1,
"queued": 0,
"pending_gate": null
}
],
"meta": { "total": 1 }
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Get a Bot
### `GET /api/v1/agent/bots/{id}`
Returns one Bot. `chief` is created on first use. The Bot's live progress is on its own session: follow `session_id` with `GET /sessions/{session_id}/stream` for its reply text and thinking as they are generated, its tool calls and the end of each turn (`event.agent_done`). `GET /bots/{id}/stream` carries only the finished log rows. There is no stop route for the Bot itself: to stop its running turn, `POST /sessions/{session_id}/cancel`. `stopBotDelegate` stops one of its delegates, not the Bot.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `global` (not tied to a realm) or a 24-hex realm id (also accepted as `?realm=`). On a session route it names the realm the session is looked up in. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header (read only when the header is absent). |
```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief" \
-H "Authorization: Bearer "
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const bot = await client.agent.bots.get('chief');
```
```json
{
"id": "chief",
"uid": "0199c3a2-6f10-7c4e-9b2a-3d5e8f1a2b4c",
"realm": "global",
"address": "bot:global/chief",
"name": "Chief",
"role": "Keeps the web app's releases moving.",
"session_id": "5f0c2a91d3b44e7f",
"model": "",
"guardrails": "Never push to main.",
"allowed_containers": [],
"allowed_agents": [],
"yolo": false,
"yolo_unapplied": [],
"created": "2026-10-08T10:30:00Z",
"created_by": { "kind": "system" },
"autonomy": { "used": 0, "max": 10, "per_delegate_max": 3, "waiting_for_you": false },
"open_delegates": 1,
"queued": 0,
"pending_gate": null
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Read a Bot's log
### `GET /api/v1/agent/bots/{id}/log`
Returns the Bot's log in order: the messages posted to it, its replies, and the delegate events it was told about. Pages by `seq`: pass `next_since` back as `since` while `has_more` is true. `POST /bots/{id}/forget` moves the rows to the archive.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `since` | query | integer | No | Return only rows whose `seq` is greater than this: the `next_since` of the previous page. Default `0`. |
| `limit` | query | integer | No | At most this many rows: 100 when omitted or 0, at most 500 (a larger value reads 500). Negative or non-integer = 400. These routes page by `since`, not by page number: `?page` is 400. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/log?since=0&limit=100" \
-H "Authorization: Bearer "
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.agent.bots.getLogIterator('chief', { since: 0, limit: 100 })) {
// ...
}
```
```json
{
"items": [
{
"at": "2026-10-08T10:31:07Z",
"role": "event",
"seq": 12,
"session_id": "9a1be4c07d2f4c11",
"text": "[report] 9a1be4c07d2f4c11 (self, default) completed: The tests pass."
}
],
"next_since": 12,
"has_more": false
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Follow a Bot's log (SSE)
### `GET /api/v1/agent/bots/{id}/stream`
Server-Sent Events of the Bot's log: first a `state` frame (the Bot as `GET /bots/{id}` returns it), then the rows after `since`, then each new row as it is written, and a new `state` frame whenever the Bot changes. Each `row` frame's id is the row's `seq`: to resume, reconnect with the `Last-Event-ID` header (an EventSource sends it by itself) or `?since=`. A resume cursor whose rows were moved to the archive in the meantime (`POST /bots/{id}/forget` or `/reset`), or one ahead of the log (the Bot was deleted and created again), is answered with a `lagged` frame (code `replay_gap`) before the rows the log still holds. A client that reads too slowly is caught up from the stored log, not dropped.
This stream carries finished rows only: the Bot's reply is one `bot` row written when its turn ends, and a turn that ends without reply text writes no row. For a chat view that shows the reply as it is written, the Bot's thinking, its tool calls and when a turn starts and ends, also follow the Bot's own session: `GET /sessions/{session_id}/stream`, with `session_id` from the `state` frame (it changes after `POST /bots/{id}/reset`). Do not answer the questions on that session whose `frame_request` kind starts with `bot.`: the Bot runtime answers them.
An `end` frame is sent when the Bot is deleted or the server ends the stream. Heartbeat comments every 15 s. The stream counts toward the per-IP stream cap (`--http-max-streams-per-ip`).
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `since` | query | integer | No | Return only rows whose `seq` is greater than this. Default `0`. |
| `Last-Event-ID` | header | string | No | SSE resume cursor: the id (a non-negative integer seq) of the last frame received. It overrides `?since`, and an SSE client sends it on reconnect. Another value is 400 `bad_request`. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
```bash
curl -N "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/stream?since=0" \
-H "Authorization: Bearer "
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const stream = await client.agent.bots.stream('chief', { since: 0 });
for await (const frame of stream) {
// ...
}
```
```text
event: state
data: {"id":"chief","uid":"0199c3a2-6f10-7c4e-9b2a-3d5e8f1a2b4c",...}
event: row
id: 12
data: {"at":"2026-10-08T10:31:07Z","role":"event","seq":12,"session_id":"9a1be4c07d2f4c11","text":"[report] 9a1be4c07d2f4c11 (self, default) completed: The tests pass."}
: heartbeat
```
The body is an SSE stream of `state`, `row` and (on resume gaps) `lagged` frames, ending with an `end` frame. Bytes are text/event-stream; the example above shows the typical shape.
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Read a Bot's archive
### `GET /api/v1/agent/bots/{id}/archive`
Returns the log rows moved out of the log by `POST /bots/{id}/forget` and `/reset`, oldest first. Pages by `seq`: pass `next_since` back as `since` while `has_more` is true. `POST /bots/{id}/purge` deletes them.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `since` | query | integer | No | Return only rows whose `seq` is greater than this: the `next_since` of the previous page. Default `0`. |
| `limit` | query | integer | No | At most this many rows: 100 when omitted or 0, at most 500 (a larger value reads 500). Negative or non-integer = 400. These routes page by `since`, not by page number: `?page` is 400. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/archive?since=0&limit=100" \
-H "Authorization: Bearer "
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.agent.bots.getArchiveIterator('chief', { since: 0, limit: 100 })) {
// ...
}
```
```json
{
"items": [
{
"at": "2026-10-08T10:31:07Z",
"role": "event",
"seq": 12,
"session_id": "9a1be4c07d2f4c11",
"text": "[report] 9a1be4c07d2f4c11 (self, default) completed: The tests pass."
}
],
"next_since": 12,
"has_more": false
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## List a Bot's delegates
### `GET /api/v1/agent/bots/{id}/delegates`
Lists the sessions the Bot opened, open and closed, by when it opened them; `state=open` or `state=closed` lists only those. Each delegate is an ordinary session: read or attach it with the `/sessions` routes. The open delegates of the page also carry how soon they read a message (`capability`) and what they are doing (`current_step`), read from their sessions as the list is served. Each delegate also carries the Bot's messages it has not read yet (`queued_commands`) and the last message or stop sent to it (`last_command`).
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `state` | query | string | No | `open` or `closed`: list only the delegates in that state. Omitted: both. |
| `page` | query | integer | No | 1-based page number for pagination. |
| `limit` | query | integer | No | Maximum items per page (0 = no pagination). |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/delegates?state=open" \
-H "Authorization: Bearer "
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.agent.bots.listDelegatesIterator('chief', { state: 'open' })) {
// ...
}
```
```json
{
"items": [
{
"agent": "",
"capability": "next_step",
"container": "",
"current_step": {
"kind": "gate",
"since": "2026-10-08T10:31:02Z"
},
"last_turn_id": "turn-p2m7-1",
"model": "hoody-ai/hoody-free",
"open_gates": [
{
"gate_id": "gate-k3x9-2",
"generation": 2,
"summary": "bash: go test ./...",
"type": "confirm"
}
],
"opened_at": "2026-10-08T10:30:40Z",
"session_id": "9a1be4c07d2f4c11",
"state": "open",
"title": "fix the flaky test"
}
],
"meta": { "total": 1 }
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Create a Bot
### `POST /api/v1/agent/bots`
Creates a Bot: a long-lived assistant that works by opening delegate sessions on containers and agents, following them, and reporting back what they did. Its own session is opened when the first message is posted. `guardrails` are limits on the work, not instructions to the Bot: they apply to the Bot and to every delegate it opens. The Bot receives them before its next message, and each delegate receives them at the top of its first prompt as limits set by the owner of the work.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `global` or a 24-hex realm id (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | No | The Bot's id. Omit it to have one generated. It cannot be changed later. To retry a create safely, pass an id: when the first try created the Bot, the retry answers 409 `bot_exists`. Pattern: `^[a-z0-9][a-z0-9_-]{0,63}$`. |
| `name` | string | No | Display name. Omitted, it is the id. |
| `role` | string | No | What the Bot is for, in one paragraph. Max length: 1000. |
| `model` | string | No | The model the Bot's session runs. Omit it for the bot agent's own model. A model no session could start with is refused 422 `model_unavailable`. |
| `guardrails` | string | No | Limits that apply to the Bot and to every delegate it opens. |
| `allowed_containers` | array of string | No | Containers delegates may run on. Empty or omitted means any. |
| `allowed_agents` | array of string | No | Agents delegates may run. Empty or omitted means any. |
| `yolo` | boolean | No | Run every delegate with YOLO mode on (approvals granted without asking). Default `false`. |
```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots" \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"id": "release-bot",
"name": "Release Bot",
"role": "Keeps the deploy pipeline moving.",
"guardrails": "Never push to main."
}'
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const bot = await client.agent.bots.create({
id: 'release-bot',
name: 'Release Bot',
role: 'Keeps the deploy pipeline moving.',
guardrails: 'Never push to main.',
});
```
```json
{
"id": "chief",
"uid": "0199c3a2-6f10-7c4e-9b2a-3d5e8f1a2b4c",
"realm": "global",
"address": "bot:global/chief",
"name": "Chief",
"role": "Keeps the web app's releases moving.",
"session_id": "5f0c2a91d3b44e7f",
"model": "",
"guardrails": "Never push to main.",
"allowed_containers": [],
"allowed_agents": [],
"yolo": false,
"yolo_unapplied": [],
"created": "2026-10-08T10:30:00Z",
"created_by": { "kind": "system" },
"autonomy": { "used": 0, "max": 10, "per_delegate_max": 3, "waiting_for_you": false },
"open_delegates": 1,
"queued": 0,
"pending_gate": null
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "bot_exists",
"message": "a Bot with this id already exists"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bot_exists` | Bot exists | A Bot with this id already exists in the realm. | Pick another id, or omit id to have one generated. |
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "model_unavailable",
"message": "unknown model \"nope/not-a-model\": a model spec starts with a provider prefix (for example hoody-ai/); list the available models with GET /api/v1/agent/models",
"details": { "reason": "unknown_model" }
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `model_unavailable` | Model unavailable | The model is not one a Bot session can start with. `details.reason`: `unknown_model` (no such provider, no model name after the prefix, or no such fusion composite) or `non_chat_provider` (the provider serves no chat models). Nothing was changed. | Pick a model from GET /models, or send an empty model for the bot agent's own. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Post a message to a Bot
### `POST /api/v1/agent/bots/{id}/messages`
Queues a message for the Bot and answers 202. When the Bot is free the message is posted within a few seconds and `turn_id` names the Bot turn it started; while the Bot is busy the answer comes at once with state `queued`, and the message is posted when the Bot's current turn ends. Each message reaches the Bot with its own id and the time it was accepted. When the Bot's turn ends, every message accepted before the Bot's next post is put together goes out in that post, in order, so a correction sent while the Bot was busy is read with the message it corrects; a message accepted after that waits for the turn after. A delegate reads at its own cutoff: everything the Bot passes on to it before its next step is read together at that step. A correction the Bot passes on after the delegate already took the first message reaches it at its following step, in order; nothing already passed on is taken back. A message sent while a gate on the Bot's own session waits for you (`pending_gate` on `GET /bots/{id}`) declines that gate, telling the Bot you sent a new message, and is posted when the Bot's turn ends.
The Bot's reply arrives on `GET /bots/{id}/stream` and in `GET /bots/{id}/log`. Only a turn started from a posted message may approve or answer a delegate's gate; a turn started from a delegate event cannot.
With an `Idempotency-Key`, a retry with the same key and text returns the same `message_id` and the message's current state, and queues nothing again; the same key with a different text is refused 422. A key is remembered while its message is queued and for at least 24 hours after the message was accepted. At most 4096 keys are remembered at once: a new keyed message beyond that is refused 429 and nothing is queued. Follow the message by `message_id`: its `user` row in the log carries it, and the `bot` row with the reply follows once its turn ends. A 503 means the message was not queued: send it again.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `Idempotency-Key` | header | string | No | Opaque retry key (1 to 255 printable ASCII, no whitespace). A retry with the same key and text returns the same `message_id` and the message's current state, and queues nothing again; the same key with a different text is 422. A key is remembered while its message is queued and for at least 24 hours after it was accepted. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `text` | string | Yes | The message (at most 64 KiB). |
```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/messages" \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-H "Idempotency-Key: bot-req-2026-10-08-001" \
-d '{
"text": "What is the status of the release?"
}'
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.bots.sendMessage('chief', {
text: 'What is the status of the release?',
}, { Idempotency-Key: 'bot-req-2026-10-08-001' });
```
```json
{
"message_id": "msg-6f1c0e2a-3b9d-4c51-9a7e-2d4b8c1f0a63",
"state": "posted",
"turn_id": "turn-k3x9-4"
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "idempotency_key_reused",
"message": "this Idempotency-Key was used for a different message",
"details": { "message_id": "msg-6f1c0e2a-3b9d-4c51-9a7e-2d4b8c1f0a63" }
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `idempotency_key_reused` | Idempotency key reused | The Idempotency-Key already accepted a different message for this Bot. `details.message_id` names the message it accepted. | Use a fresh Idempotency-Key for a different message, or resend the identical message to get its receipt. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
| `idempotency_keys_exhausted` | Too many remembered keys | The Bot already remembers 4096 Idempotency-Keys (each one while its message is queued and for at least 24 hours after the message was accepted). Nothing was queued. | Send the message later, when older keys have expired, or send it without an Idempotency-Key. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Change a Bot's settings
### `PATCH /api/v1/agent/bots/{id}`
Changes the fields the body names. `name` and `role` are announced to the Bot before its next message. `id`, `uid`, `realm`, `created`, `created_at` and `created_by` never change: a body that names one is refused 400 `field_immutable` and nothing changes. `yolo` applies at once to every open delegate. `allowed_containers` and `allowed_agents` apply to the next delegate the Bot opens. `model` applies to the Bot's next session: the running session keeps its model until `POST /bots/{id}/reset` starts a new one. Guardrails are changed with `PUT /bots/{id}/guardrails`.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | No | Display name. |
| `role` | string | No | What the Bot is for, in one paragraph. Empty clears it. Max length: 1000. |
| `model` | string | No | The model of the Bot's next session, started by `POST /bots/{id}/reset`; the running session keeps its model. Empty for the bot agent's own model. A model no session could start with is refused 422 `model_unavailable` and nothing changes. |
| `allowed_containers` | array of string | No | Containers delegates may run on. Empty means any. |
| `allowed_agents` | array of string | No | Agents delegates may run. Empty means any. |
| `yolo` | boolean | No | YOLO mode for every delegate. |
```bash
curl -X PATCH "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief" \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"name": "Chief",
"role": "Keeps the web app's releases moving."
}'
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const bot = await client.agent.bots.update('chief', {
name: 'Chief',
role: 'Keeps the web app\'s releases moving.',
});
```
```json
{
"id": "chief",
"uid": "0199c3a2-6f10-7c4e-9b2a-3d5e8f1a2b4c",
"realm": "global",
"address": "bot:global/chief",
"name": "Chief",
"role": "Keeps the web app's releases moving.",
"session_id": "5f0c2a91d3b44e7f",
"model": "openai/gpt-5",
"guardrails": "Never push to main.",
"allowed_containers": [],
"allowed_agents": [],
"yolo": false,
"yolo_unapplied": [],
"created": "2026-10-08T10:30:00Z",
"created_by": { "kind": "system" },
"autonomy": { "used": 0, "max": 10, "per_delegate_max": 3, "waiting_for_you": false },
"open_delegates": 1,
"queued": 0,
"pending_gate": null,
"model_applies": "after_reset"
}
```
```json
{
"code": "field_immutable",
"message": "uid is fixed when the Bot is created and cannot be changed",
"details": { "field": "uid" }
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
| `field_immutable` | Field cannot be changed | The body names a field fixed when the Bot was created: `id`, `uid`, `realm`, `created`, `created_at` or `created_by`. `details.field` names it. Nothing was changed. | Leave the field out. To use another id or realm, create a new Bot. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "model_unavailable",
"message": "unknown model \"nope/not-a-model\": a model spec starts with a provider prefix (for example hoody-ai/); list the available models with GET /api/v1/agent/models",
"details": { "reason": "unknown_model" }
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `model_unavailable` | Model unavailable | The model is not one a Bot session can start with. `details.reason`: `unknown_model` (no such provider, no model name after the prefix, or no such fusion composite) or `non_chat_provider` (the provider serves no chat models). Nothing was changed. | Pick a model from GET /models, or send an empty model for the bot agent's own. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Replace a Bot's guardrails
### `PUT /api/v1/agent/bots/{id}/guardrails`
Replaces the Bot's guardrails: limits on the work that apply to the Bot and to every delegate it opens, not instructions to the Bot. The Bot receives the new text, each line marked `[guardrails updated]`, before its next message (an empty text is announced as `none`). Delegates opened from then on receive it at the top of their first prompt as limits set by the owner of the work; open delegates keep the limits they were given.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `guardrails` | string | Yes | The new limits; empty clears them. |
```bash
curl -X PUT "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/guardrails" \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"guardrails": "Never push to main."
}'
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const bot = await client.agent.bots.setGuardrails('chief', {
guardrails: 'Never push to main.',
});
```
```json
{
"id": "chief",
"uid": "0199c3a2-6f10-7c4e-9b2a-3d5e8f1a2b4c",
"realm": "global",
"address": "bot:global/chief",
"name": "Chief",
"role": "Keeps the web app's releases moving.",
"session_id": "5f0c2a91d3b44e7f",
"model": "",
"guardrails": "Never push to main.",
"allowed_containers": [],
"allowed_agents": [],
"yolo": false,
"yolo_unapplied": [],
"created": "2026-10-08T10:30:00Z",
"created_by": { "kind": "system" },
"autonomy": { "used": 0, "max": 10, "per_delegate_max": 3, "waiting_for_you": false },
"open_delegates": 1,
"queued": 0,
"pending_gate": null
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Make a Bot forget its conversation
### `POST /api/v1/agent/bots/{id}/forget`
Moves the log to the archive and clears the Bot session's conversation (posted as `/clear` when the Bot is free). The Bot keeps its settings and its delegates, and receives its guardrails again before the next message.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/forget" \
-H "Authorization: Bearer "
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.bots.forget('chief');
```
```json
{
"archived": 12
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Give a Bot a new session
### `POST /api/v1/agent/bots/{id}/reset`
Moves the log to the archive and starts a new session for the Bot (with its current model). Its delegates stay its own; messages not yet posted are posted to the new session. The old session is not closed.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/reset" \
-H "Authorization: Bearer "
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.bots.reset('chief');
```
```json
{
"archived": 30,
"session_id": "c71d0e5a2b9f4a08"
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Delete a Bot's archive
### `POST /api/v1/agent/bots/{id}/purge`
Deletes the rows `forget` and `reset` moved to the archive. The log is not touched.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/purge" \
-H "Authorization: Bearer "
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.bots.purgeArchive('chief');
```
```json
{
"purged": true
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Stop one of a Bot's delegates now
### `POST /api/v1/agent/bots/{id}/delegates/{sid}/stop`
Stops the delegate's work at once, whatever the Bot is doing: its running turn, its background tasks, workflow runs and shells end, its loops pause, and messages from the Bot it has not read yet are dropped. With `close` true its session is then closed. It stops a delegate, never the Bot: to stop the Bot's own running turn, `POST /sessions/{session_id}/cancel` on the Bot's `session_id` (`GET /bots/{id}`). Answers 200 once the work is stopped, or 202 when the session is still stopping it after a few seconds (it finishes without you; `GET /bots/{id}/delegates` shows the outcome in `last_command`). The Bot learns of it from the delegate's report.
With an `Idempotency-Key`, sending the same stop again returns its outcome and stops nothing twice; the same key with a different `close` is 422. A 503 means the stop was not sent yet: send it again with the same key.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `sid` | path | string | Yes | The delegate id. |
| `Idempotency-Key` | header | string | No | Opaque retry key (1 to 255 printable ASCII, no whitespace). The same stop sent again with the same key returns its outcome; the same key with a different `close` is 422. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `close` | boolean | No | Also close the delegate's session for good. Default `false`. |
```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/delegates/9a1be4c07d2f4c11/stop" \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-H "Idempotency-Key: stop-2026-10-08-001" \
-d '{
"close": true
}'
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.bots.stopDelegate('chief', '9a1be4c07d2f4c11', {
close: true,
}, { Idempotency-Key: 'stop-2026-10-08-001' });
```
```json
{
"closed": false,
"command_id": "cmd_3f2a9c1e5b7d40a1c2e8f6d9",
"session_id": "9a1be4c07d2f4c11",
"state": "committed",
"stopped": [
{
"id": "9a1be4c07d2f4c11",
"kind": "session",
"outcome": "stopped"
}
],
"title": "fix the flaky test"
}
```
```json
{
"closed": false,
"command_id": "cmd_3f2a9c1e5b7d40a1c2e8f6d9",
"session_id": "9a1be4c07d2f4c11",
"state": "committed",
"stopped": [
{
"id": "9a1be4c07d2f4c11",
"kind": "session",
"outcome": "stopped"
}
],
"title": "fix the flaky test"
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
| `session_closed` | Session closed | The session is closed (not live), or a stop sent with `close` is closing it. Nothing was admitted. | Attach the session with POST /sessions (attach) and send the command again, or start another session. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "idempotency_key_reused",
"message": "this Idempotency-Key was used for a different stop"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `idempotency_key_reused` | Idempotency key reused | The Idempotency-Key was already used for a different stop of this Bot (another delegate, or another `close`). Nothing was sent. | Use a fresh Idempotency-Key for a different stop, or resend the identical stop to get its outcome. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Delete a Bot
### `DELETE /api/v1/agent/bots/{id}`
Deletes the Bot and its log and archive. First, best effort and for at most a few seconds: every open delegate that is working or waiting on an approval is stopped, an approval the stop did not reach is declined, and auto-approve is turned off on the delegates the Bot turned it on for. Its session and its delegates are not closed: they stay listed under `GET /sessions`, a person can continue them, and they end like any idle session. Deleting `chief` removes it; the next use creates a new one.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | The bot id. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header. |
```bash
curl -X DELETE "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief" \
-H "Authorization: Bearer "
```
```ts
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.agent.bots.delete('chief');
```
The response has no body.
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Open a Bot URL (paste into an app)
Every Bot has a per-realm URL ready to paste as the base URL of an OpenAI client, an Anthropic client, or an MCP client. `realm` is `global` or a 24-hex realm id; the short form `…/bots/{bot}/…` uses the agent's current realm instead, and a Bot whose id is `global` or 24 hex is reached by the long form only. `GET /bots/{id}` lists a Bot's URLs under `urls`. The path is read forgivingly: repeated slashes, a trailing slash, a missing or doubled `/v1`, and a whole `…/chat/completions` or `…/messages` URL pasted as the base all reach the same Bot. The `Authorization` and `x-api-key` headers are ignored: apps may send any value, and access is decided before the request reaches the agent.
The MCP server is stateless: it sends no event stream and keeps no session. POST each request, end the session with a DELETE that the server answers 405 (Allow: POST). A GET of `/mcp` with `Accept: text/event-stream` answers 405 (Allow: POST).
Only the last message is read, and it must be a user message with text only (an image, audio or file part is refused 400). A side request an app makes on the model (a conversation title, a summary, follow-up suggestions, tags, or an autocompletion: recognised by its system prompt or task text, or by a `max_tokens` of 64 or less) is not posted: the Bot's model answers it in one completion, outside the Bot's history. System prompts, client tools, sampling settings and `max_tokens` are otherwise ignored, and a `tool_choice` that forces a tool is refused 400. With `stream: true` the body becomes a Server-Sent Events stream of the same reply.
A request sent again with the same messages within 2 minutes, while its answer was never delivered in full, gets that answer without posting again. An `Idempotency-Key` header names the message instead: the same key always gets the same answer. A non-streamed request waits up to 100 seconds for the reply; a streamed one sends keepalives and waits up to 10 minutes. The Bot is one conversation shared by every app that talks to it.
### `GET /api/v1/agent/bots/{realm}/{bot}`
A GET of the Bot URL answers a short text page that starts with `This is the URL of Bot (bot:/)` and says what to paste where. Model doors (`/v1/models`) return the one-model list in the client format. A GET of `/mcp` with `Accept: text/event-stream` answers 405 (Allow: POST).
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `realm` | path | string | Yes | The realm. |
| `bot` | path | string | Yes | The bot. |
```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/global/chief" \
-H "Authorization: Bearer "
```
```text
This is the URL of Bot Chief (bot:global/chief).
Paste /v1 as the base URL of an OpenAI client (chat completions, responses, models).
Paste as the base URL of an Anthropic client (Messages, models).
Paste /mcp into an MCP client.
```
A GET of a model door returns `application/json` in the client format (chat completion, Responses response, Anthropic message, or the one-model list). The help page example above shows the typical text/plain shape.
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "method_not_allowed",
"message": "this MCP server sends no stream and keeps no session: POST each request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `method_not_allowed` | Method not allowed | The MCP server is stateless: it sends no event stream and has no session to end. The answer carries Allow: POST. | POST each MCP request. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
### `GET /api/v1/agent/bots/{realm}/{bot}/{door}`
A GET of a Bot door returns the help page (text/plain) on the base URL, chat URL, and an unknown door; returns the one-model list (application/json) on a model door; and answers 405 (Allow: POST) on `/mcp` with `Accept: text/event-stream`. An unknown door returns 404.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `realm` | path | string | Yes | The realm. |
| `bot` | path | string | Yes | The bot. |
| `door` | path | string | Yes | The door. |
```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/global/chief/v1/models" \
-H "Authorization: Bearer "
```
```text
This is the URL of Bot Chief (bot:global/chief).
Paste /v1 as the base URL of an OpenAI client (chat completions, responses, models).
Paste as the base URL of an Anthropic client (Messages, models).
Paste /mcp into an MCP client.
```
A GET of a model door returns `application/json` in the client format (chat completion, Responses response, Anthropic message, or the one-model list). The help page example above shows the typical text/plain shape returned by the base URL, chat URL, and an unknown door.
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "method_not_allowed",
"message": "this MCP server sends no stream and keeps no session: POST each request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `method_not_allowed` | Method not allowed | The MCP server is stateless: it sends no event stream and has no session to end. The answer carries Allow: POST. | POST each MCP request. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
### `POST /api/v1/agent/bots/{realm}/{bot}`
Posts the last user message of the body to the Bot. The body uses the OpenAI, Anthropic, or MCP client format. With `stream: true` the answer is a Server-Sent Events stream; otherwise a JSON body. A POST of `/mcp` is an MCP request. The Authorization and x-api-key headers are ignored: apps may send any value, and access is decided before the request reaches the agent.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `realm` | path | string | Yes | The realm. |
| `bot` | path | string | Yes | The bot. |
```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/global/chief/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer " \
-H "Idempotency-Key: req-2026-10-08-001" \
-d '{
"model": "bot:global/chief",
"messages": [
{ "role": "user", "content": "What is the status of the release?" }
]
}'
```
```json
{
"id": "chatcmpl-bot-0199c3a2",
"object": "chat.completion",
"created": 1762533000,
"model": "bot:global/chief",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "I'll start by checking the current state of the release pipeline."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 18,
"total_tokens": 60
}
}
```
The body is `application/json` (a chat completion, Responses response, Anthropic message, or MCP reply) when `stream` is unset or false, and `text/event-stream` when the body sets `stream` to true. Chat and model errors use the client format's own error body (OpenAI, or Anthropic for `/v1/messages` and for a model request carrying `anthropic-version`).
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "model_not_found",
"message": "no such model: no Bot with this id in a realm this login serves"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
| `model_not_found` | No such model | No Bot has this id in a realm this login serves. | List the models and use one of their ids. |
```json
{
"code": "method_not_allowed",
"message": "this MCP server sends no stream and keeps no session: POST each request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `method_not_allowed` | Method not allowed | The MCP server is stateless: it sends no event stream and has no session to end. The answer carries Allow: POST. | POST each MCP request. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "idempotency_key_reused",
"message": "this Idempotency-Key was used for a different message",
"details": { "message_id": "msg-6f1c0e2a-3b9d-4c51-9a7e-2d4b8c1f0a63" }
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `idempotency_key_reused` | Idempotency key reused | The Idempotency-Key already accepted a different message for this Bot. `details.message_id` names the message it accepted. | Use a fresh Idempotency-Key for a different message, or resend the identical message to get its receipt. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
| `idempotency_keys_exhausted` | Too many remembered keys | The Bot already remembers 4096 Idempotency-Keys (each one while its message is queued and for at least 24 hours after the message was accepted). Nothing was queued. | Send the message later, when older keys have expired, or send it without an Idempotency-Key. |
| `bot_busy` | Bot busy | The Bot was answering other requests for the whole wait, so this message was not posted. | Send the request again in a moment. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
### `POST /api/v1/agent/bots/{realm}/{bot}/{door}`
Posts the last user message of the body to the Bot through the named door. With `stream: true` the answer is a Server-Sent Events stream; otherwise a JSON body in the client format of the door. A POST of `/mcp` is an MCP request. The Authorization and x-api-key headers are ignored: apps may send any value, and access is decided before the request reaches the agent.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `realm` | path | string | Yes | The realm. |
| `bot` | path | string | Yes | The bot. |
| `door` | path | string | Yes | The door. |
```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/global/chief/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer " \
-H "Idempotency-Key: req-2026-10-08-002" \
-d '{
"model": "bot:global/chief",
"messages": [
{ "role": "user", "content": "What is the status of the release?" }
]
}'
```
```json
{
"id": "chatcmpl-bot-0199c3a2",
"object": "chat.completion",
"created": 1762533000,
"model": "bot:global/chief",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "I'll start by checking the current state of the release pipeline."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 18,
"total_tokens": 60
}
}
```
The body is `application/json` (a chat completion, Responses response, Anthropic message, or MCP reply) when `stream` is unset or false, and `text/event-stream` when the body sets `stream` to true. Chat and model errors use the client format's own error body (OpenAI, or Anthropic for `/v1/messages` and for a model request carrying `anthropic-version`).
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "model_not_found",
"message": "no such model: no Bot with this id in a realm this login serves"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
| `model_not_found` | No such model | No Bot has this id in a realm this login serves. | List the models and use one of their ids. |
```json
{
"code": "method_not_allowed",
"message": "this MCP server sends no stream and keeps no session: POST each request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `method_not_allowed` | Method not allowed | The MCP server is stateless: it sends no event stream and has no session to end. The answer carries Allow: POST. | POST each MCP request. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "idempotency_key_reused",
"message": "this Idempotency-Key was used for a different message",
"details": { "message_id": "msg-6f1c0e2a-3b9d-4c51-9a7e-2d4b8c1f0a63" }
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `idempotency_key_reused` | Idempotency key reused | The Idempotency-Key already accepted a different message for this Bot. `details.message_id` names the message it accepted. | Use a fresh Idempotency-Key for a different message, or resend the identical message to get its receipt. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
| `idempotency_keys_exhausted` | Too many remembered keys | The Bot already remembers 4096 Idempotency-Keys (each one while its message is queued and for at least 24 hours after the message was accepted). Nothing was queued. | Send the message later, when older keys have expired, or send it without an Idempotency-Key. |
| `bot_busy` | Bot busy | The Bot was answering other requests for the whole wait, so this message was not posted. | Send the request again in a moment. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
### `DELETE /api/v1/agent/bots/{realm}/{bot}`
The MCP server is stateless and has no session to end, so a DELETE of `/mcp` or the base URL answers 405 (Allow: POST). A DELETE of any other door is 404. The 200 response is documented for completeness but is not actually returned in practice.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `realm` | path | string | Yes | The realm. |
| `bot` | path | string | Yes | The bot. |
```bash
curl -X DELETE "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/global/chief/mcp" \
-H "Authorization: Bearer "
```
Not actually returned. The MCP server is stateless: DELETE of `/mcp` or the base URL answers 405 (Allow: POST), and DELETE of any other door is 404.
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "method_not_allowed",
"message": "this MCP server sends no stream and keeps no session: POST each request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `method_not_allowed` | Method not allowed | The MCP server is stateless: it sends no event stream and has no session to end. The answer carries Allow: POST. | POST each MCP request. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
### `DELETE /api/v1/agent/bots/{realm}/{bot}/{door}`
The MCP server is stateless and has no session to end, so a DELETE of `/mcp` or the base URL answers 405 (Allow: POST). A DELETE of any other door is 404. The 200 response is documented for completeness but is not actually returned in practice.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `realm` | path | string | Yes | The realm. |
| `bot` | path | string | Yes | The bot. |
| `door` | path | string | Yes | The door. |
```bash
curl -X DELETE "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/global/chief/mcp" \
-H "Authorization: Bearer "
```
Not actually returned. The MCP server is stateless: DELETE of `/mcp` or the base URL answers 405 (Allow: POST), and DELETE of any other door is 404.
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "method_not_allowed",
"message": "this MCP server sends no stream and keeps no session: POST each request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `method_not_allowed` | Method not allowed | The MCP server is stateless: it sends no event stream and has no session to end. The answer carries Allow: POST. | POST each MCP request. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## Anthropic-compatibility API
The compat paths under `/api/v1/agent/compat/anthropic/v1/...` reach the same Bots as the Bot URL. Each Bot is a model whose id is its address, `bot:/` (for example `bot:global/chief`); a bare Bot id names a Bot of the realm `X-Hoody-Realm` names, else of the agent's current realm. The `Authorization` and `x-api-key` headers are ignored: apps may send any value, and access is decided before the request reaches the agent. Errors use this format's own error body, not the agent's. The path is read forgivingly: repeated slashes, a trailing slash, a missing or doubled `/v1`, and a whole `…/chat/completions` or `…/messages` URL pasted as the base all reach the same route.
### `GET /api/v1/agent/compat/anthropic/v1/models`
Lists every Bot of every realm this login serves (an agent pinned to one realm: that realm's Bots), in the Anthropic-compatible model list shape.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `global` (not tied to a realm) or a 24-hex realm id (also accepted as `?realm=`). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 `not_found`. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header (read only when the header is absent): `global` (not tied to a realm) or a 24-hex realm id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/compat/anthropic/v1/models" \
-H "x-api-key: "
```
```json
{
"data": [
{
"type": "model",
"id": "bot:global/chief",
"display_name": "Chief",
"created_at": "2026-10-08T10:30:00Z"
}
],
"has_more": false,
"first_id": "bot:global/chief",
"last_id": "bot:global/chief"
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
### `GET /api/v1/agent/compat/anthropic/v1/models/{model}`
Reads the Bot named `model` in the Anthropic-compatible model shape. An unknown Bot, or a realm this login does not serve, answers 404.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `model` | path | string | Yes | The model. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `global` (not tied to a realm) or a 24-hex realm id (also accepted as `?realm=`). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 `not_found`. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header (read only when the header is absent): `global` (not tied to a realm) or a 24-hex realm id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/compat/anthropic/v1/models/bot:global/chief" \
-H "x-api-key: "
```
```json
{
"type": "model",
"id": "bot:global/chief",
"display_name": "Chief",
"created_at": "2026-10-08T10:30:00Z"
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "model_not_found",
"message": "no such model: no Bot with this id in a realm this login serves"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
| `model_not_found` | No such model | No Bot has this id in a realm this login serves. | List the models and use one of their ids. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
### `POST /api/v1/agent/compat/anthropic/v1/messages`
Posts the last user message to the Bot the model names and answers with the Bot's reply, in the Anthropic Messages shape: JSON, or Server-Sent Events when the body sets `stream` to true. With `stream` true, the body carries the `message_start` through `message_stop` events instead of a JSON message.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `global` (not tied to a realm) or a 24-hex realm id (also accepted as `?realm=`). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 `not_found`. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header (read only when the header is absent): `global` (not tied to a realm) or a 24-hex realm id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/compat/anthropic/v1/messages" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-H "x-api-key: " \
-H "Idempotency-Key: req-2026-10-08-003" \
-d '{
"model": "bot:global/chief",
"max_tokens": 1024,
"messages": [
{ "role": "user", "content": "What is the status of the release?" }
]
}'
```
```json
{
"id": "msg_bot_0199c3a2",
"type": "message",
"role": "assistant",
"model": "bot:global/chief",
"content": [
{
"type": "text",
"text": "I'll start by checking the current state of the release pipeline."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 42,
"output_tokens": 18,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0
}
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "model_not_found",
"message": "no such model: no Bot with this id in a realm this login serves"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
| `model_not_found` | No such model | No Bot has this id in a realm this login serves. | List the models and use one of their ids. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "idempotency_key_reused",
"message": "this Idempotency-Key was used for a different message",
"details": { "message_id": "msg-6f1c0e2a-3b9d-4c51-9a7e-2d4b8c1f0a63" }
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `idempotency_key_reused` | Idempotency key reused | The Idempotency-Key already accepted a different message for this Bot. `details.message_id` names the message it accepted. | Use a fresh Idempotency-Key for a different message, or resend the identical message to get its receipt. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
| `idempotency_keys_exhausted` | Too many remembered keys | The Bot already remembers 4096 Idempotency-Keys (each one while its message is queued and for at least 24 hours after the message was accepted). Nothing was queued. | Send the message later, when older keys have expired, or send it without an Idempotency-Key. |
| `bot_busy` | Bot busy | The Bot was answering other requests for the whole wait, so this message was not posted. | Send the request again in a moment. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
---
## OpenAI-compatibility API
The compat paths under `/api/v1/agent/compat/openai/v1/...` reach the same Bots as the Bot URL. Each Bot is a model whose id is its address, `bot:/` (for example `bot:global/chief`); a bare Bot id names a Bot of the realm `X-Hoody-Realm` names, else of the agent's current realm. The `Authorization` and `x-api-key` headers are ignored: apps may send any value, and access is decided before the request reaches the agent. Errors use this format's own error body, not the agent's. The path is read forgivingly: repeated slashes, a trailing slash, a missing or doubled `/v1`, and a whole `…/chat/completions` or `…/messages` URL pasted as the base all reach the same route.
### `GET /api/v1/agent/compat/openai/v1/models`
Lists every Bot of every realm this login serves (an agent pinned to one realm: that realm's Bots), in the OpenAI-compatible model list shape.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `global` (not tied to a realm) or a 24-hex realm id (also accepted as `?realm=`). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 `not_found`. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header (read only when the header is absent): `global` (not tied to a realm) or a 24-hex realm id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/compat/openai/v1/models" \
-H "Authorization: Bearer "
```
```json
{
"object": "list",
"data": [
{
"id": "bot:global/chief",
"object": "model",
"created": 1762533000,
"owned_by": "hoody"
}
]
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "not_found",
"message": "resource not found"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
### `GET /api/v1/agent/compat/openai/v1/models/{model}`
Reads the Bot named `model` in the OpenAI-compatible model shape. An unknown Bot, or a realm this login does not serve, answers 404.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `model` | path | string | Yes | The model. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `global` (not tied to a realm) or a 24-hex realm id (also accepted as `?realm=`). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 `not_found`. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header (read only when the header is absent): `global` (not tied to a realm) or a 24-hex realm id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
```bash
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/compat/openai/v1/models/bot:global/chief" \
-H "Authorization: Bearer "
```
```json
{
"id": "bot:global/chief",
"object": "model",
"created": 1762533000,
"owned_by": "hoody"
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "model_not_found",
"message": "no such model: no Bot with this id in a realm this login serves"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
| `model_not_found` | No such model | No Bot has this id in a realm this login serves. | List the models and use one of their ids. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
### `POST /api/v1/agent/compat/openai/v1/chat/completions`
Posts the last user message to the Bot the model names and answers with the Bot's reply, in the OpenAI chat completions shape: JSON, or Server-Sent Events when the body sets `stream` to true. With `stream` true, the body carries the `chat.completion.chunk` events ending in `[DONE]` instead of a JSON completion.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `global` (not tied to a realm) or a 24-hex realm id (also accepted as `?realm=`). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 `not_found`. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header (read only when the header is absent): `global` (not tied to a realm) or a 24-hex realm id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/compat/openai/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer " \
-H "Idempotency-Key: req-2026-10-08-004" \
-d '{
"model": "bot:global/chief",
"messages": [
{ "role": "user", "content": "What is the status of the release?" }
]
}'
```
```json
{
"id": "chatcmpl-bot-0199c3a2",
"object": "chat.completion",
"created": 1762533000,
"model": "bot:global/chief",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "I'll start by checking the current state of the release pipeline."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 18,
"total_tokens": 60
}
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "model_not_found",
"message": "no such model: no Bot with this id in a realm this login serves"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
| `model_not_found` | No such model | No Bot has this id in a realm this login serves. | List the models and use one of their ids. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "idempotency_key_reused",
"message": "this Idempotency-Key was used for a different message",
"details": { "message_id": "msg-6f1c0e2a-3b9d-4c51-9a7e-2d4b8c1f0a63" }
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `idempotency_key_reused` | Idempotency key reused | The Idempotency-Key already accepted a different message for this Bot. `details.message_id` names the message it accepted. | Use a fresh Idempotency-Key for a different message, or resend the identical message to get its receipt. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
| `idempotency_keys_exhausted` | Too many remembered keys | The Bot already remembers 4096 Idempotency-Keys (each one while its message is queued and for at least 24 hours after the message was accepted). Nothing was queued. | Send the message later, when older keys have expired, or send it without an Idempotency-Key. |
| `bot_busy` | Bot busy | The Bot was answering other requests for the whole wait, so this message was not posted. | Send the request again in a moment. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
### `POST /api/v1/agent/compat/openai/v1/responses`
Posts the last user message of `input` (a string, or a list of messages) to the Bot the model names and answers with the Bot's reply in the OpenAI Responses shape: JSON, or Server-Sent Events when the body sets `stream` to true. Nothing is stored: `previous_response_id` and `store` are ignored. With `stream` true, the body carries the `response.created` through `response.completed` events instead of a JSON response.
### Parameters
| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `global` (not tied to a realm) or a 24-hex realm id (also accepted as `?realm=`). On a session route it names the realm the session is looked up in: a session in another realm, or in a realm this login does not serve, is 404 `not_found`. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
| `realm` | query | string | No | Per-request realm selector, the `in:query` alias of the `X-Hoody-Realm` header (read only when the header is absent): `global` (not tied to a realm) or a 24-hex realm id. Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes, and for any other realm on an agent pinned to one realm. |
```bash
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/compat/openai/v1/responses" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer " \
-H "Idempotency-Key: req-2026-10-08-005" \
-d '{
"model": "bot:global/chief",
"input": "What is the status of the release?"
}'
```
```json
{
"id": "resp_bot_0199c3a2",
"object": "response",
"created_at": 1762533000,
"status": "completed",
"model": "bot:global/chief",
"output": [
{
"type": "message",
"id": "msg_bot_0199c3a2",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "I'll start by checking the current state of the release pipeline."
}
]
}
],
"output_text": "I'll start by checking the current state of the release pipeline.",
"usage": {
"input_tokens": 42,
"output_tokens": 18,
"total_tokens": 60
}
}
```
```json
{
"code": "bad_request",
"message": "invalid request"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request scope header this route does not take: a realm or container header on a route without that dimension, or, on an agent pinned to one realm, a different realm. | Omit the header, or name the realm the agent is pinned to. |
| `invalid_realm` | Invalid realm selector | The realm selector is malformed (not `""`/`"global"`/a 24-hex id). | Pass a valid realm selector. |
```json
{
"code": "forbidden",
"message": "request must arrive through the Hoody proxy"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (Source IP Guard) | Refused by the Source IP Guard: Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. |
```json
{
"code": "model_not_found",
"message": "no such model: no Bot with this id in a realm this login serves"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. |
| `model_not_found` | No such model | No Bot has this id in a realm this login serves. | List the models and use one of their ids. |
```json
{
"code": "realm_blocked",
"message": "realm is blocked"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `realm_blocked` | Realm blocked | The requested realm is blocked or corrupt; the request fails closed before dispatch. | Select a valid, reachable realm. |
```json
{
"code": "payload_too_large",
"message": "request body exceeds the configured size limit"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (MaxBodyBytes). The gateway rejects an oversized body at the edge (http.MaxBytesReader) before the handler reads it; a well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
```json
{
"code": "idempotency_key_reused",
"message": "this Idempotency-Key was used for a different message",
"details": { "message_id": "msg-6f1c0e2a-3b9d-4c51-9a7e-2d4b8c1f0a63" }
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `idempotency_key_reused` | Idempotency key reused | The Idempotency-Key already accepted a different message for this Bot. `details.message_id` names the message it accepted. | Use a fresh Idempotency-Key for a different message, or resend the identical message to get its receipt. |
```json
{
"code": "rate_limited",
"message": "request rate limit exceeded"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. |
| `idempotency_keys_exhausted` | Too many remembered keys | The Bot already remembers 4096 Idempotency-Keys (each one while its message is queued and for at least 24 hours after the message was accepted). Nothing was queued. | Send the message later, when older keys have expired, or send it without an Idempotency-Key. |
| `bot_busy` | Bot busy | The Bot was answering other requests for the whole wait, so this message was not posted. | Send the request again in a moment. |
```json
{
"code": "internal_error",
"message": "internal server error"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
```json
{
"code": "service_unavailable",
"message": "service unavailable"
}
```
| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. |
| `restriction_unknown` | Login restriction unknown | The agent cannot read yet which realms its login may serve (GET /hoody/auth/status reports restriction `unknown`), so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |