Agent: Bots
Section titled “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 and read with the sessions routes (see 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 (<base>/v1), an Anthropic client (<base>), or an MCP client (<base>/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
Section titled “List Bots”GET /api/v1/agent/bots
Section titled “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
Section titled “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. |
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots?realm=global" \ -H "Authorization: Bearer <token>"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' })) { // ...}{ "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 }}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Get a Bot”GET /api/v1/agent/bots/{id}
Section titled “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
Section titled “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). |
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief" \ -H "Authorization: Bearer <token>"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');{ "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}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Read a Bot’s log”GET /api/v1/agent/bots/{id}/log
Section titled “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
Section titled “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. |
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 <token>"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 })) { // ...}{ "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}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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)
Section titled “Follow a Bot’s log (SSE)”GET /api/v1/agent/bots/{id}/stream
Section titled “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=<last id>. 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
Section titled “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. |
curl -N "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/stream?since=0" \ -H "Authorization: Bearer <token>"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) { // ...}event: statedata: {"id":"chief","uid":"0199c3a2-6f10-7c4e-9b2a-3d5e8f1a2b4c",...}
event: rowid: 12data: {"at":"2026-10-08T10:31:07Z","role":"event","seq":12,"session_id":"9a1be4c07d2f4c11","text":"[report] 9a1be4c07d2f4c11 (self, default) completed: The tests pass."}
: heartbeatThe 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.
{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Read a Bot’s archive”GET /api/v1/agent/bots/{id}/archive
Section titled “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
Section titled “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. |
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 <token>"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 })) { // ...}{ "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}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “List a Bot’s delegates”GET /api/v1/agent/bots/{id}/delegates
Section titled “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
Section titled “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. |
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/delegates?state=open" \ -H "Authorization: Bearer <token>"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' })) { // ...}{ "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 }}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Create a Bot”POST /api/v1/agent/bots
Section titled “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
Section titled “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
Section titled “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. |
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "id": "release-bot", "name": "Release Bot", "role": "Keeps the deploy pipeline moving.", "guardrails": "Never push to main." }'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.',});{ "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}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Post a message to a Bot”POST /api/v1/agent/bots/{id}/messages
Section titled “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
Section titled “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
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | The message (at most 64 KiB). |
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/messages" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: bot-req-2026-10-08-001" \ -d '{ "text": "What is the status of the release?" }'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' });{ "message_id": "msg-6f1c0e2a-3b9d-4c51-9a7e-2d4b8c1f0a63", "state": "posted", "turn_id": "turn-k3x9-4"}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Change a Bot’s settings”PATCH /api/v1/agent/bots/{id}
Section titled “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
Section titled “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
Section titled “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. |
curl -X PATCH "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "name": "Chief", "role": "Keeps the web app's releases moving." }'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.',});{ "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"}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Replace a Bot’s guardrails”PUT /api/v1/agent/bots/{id}/guardrails
Section titled “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
Section titled “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
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
guardrails | string | Yes | The new limits; empty clears them. |
curl -X PUT "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/guardrails" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "guardrails": "Never push to main." }'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.',});{ "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}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Make a Bot forget its conversation”POST /api/v1/agent/bots/{id}/forget
Section titled “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
Section titled “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. |
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/forget" \ -H "Authorization: Bearer <token>"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');{ "archived": 12}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Give a Bot a new session”POST /api/v1/agent/bots/{id}/reset
Section titled “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
Section titled “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. |
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/reset" \ -H "Authorization: Bearer <token>"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');{ "archived": 30, "session_id": "c71d0e5a2b9f4a08"}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Delete a Bot’s archive”POST /api/v1/agent/bots/{id}/purge
Section titled “POST /api/v1/agent/bots/{id}/purge”Deletes the rows forget and reset moved to the archive. The log is not touched.
Parameters
Section titled “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. |
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/purge" \ -H "Authorization: Bearer <token>"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');{ "purged": true}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Stop one of a Bot’s delegates now”POST /api/v1/agent/bots/{id}/delegates/{sid}/stop
Section titled “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
Section titled “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
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
close | boolean | No | Also close the delegate’s session for good. Default false. |
curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief/delegates/9a1be4c07d2f4c11/stop" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: stop-2026-10-08-001" \ -d '{ "close": true }'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' });{ "closed": false, "command_id": "cmd_3f2a9c1e5b7d40a1c2e8f6d9", "session_id": "9a1be4c07d2f4c11", "state": "committed", "stopped": [ { "id": "9a1be4c07d2f4c11", "kind": "session", "outcome": "stopped" } ], "title": "fix the flaky test"}{ "closed": false, "command_id": "cmd_3f2a9c1e5b7d40a1c2e8f6d9", "session_id": "9a1be4c07d2f4c11", "state": "committed", "stopped": [ { "id": "9a1be4c07d2f4c11", "kind": "session", "outcome": "stopped" } ], "title": "fix the flaky test"}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “Delete a Bot”DELETE /api/v1/agent/bots/{id}
Section titled “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
Section titled “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. |
curl -X DELETE "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/chief" \ -H "Authorization: Bearer <token>"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.
{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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)
Section titled “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}
Section titled “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 <name> (bot:<realm>/<id>) 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
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
realm | path | string | Yes | The realm. |
bot | path | string | Yes | The bot. |
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/global/chief" \ -H "Authorization: Bearer <token>"This is the URL of Bot Chief (bot:global/chief).
Paste <base>/v1 as the base URL of an OpenAI client (chat completions, responses, models).Paste <base> as the base URL of an Anthropic client (Messages, models).Paste <base>/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.
{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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}
Section titled “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
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
realm | path | string | Yes | The realm. |
bot | path | string | Yes | The bot. |
door | path | string | Yes | The door. |
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/global/chief/v1/models" \ -H "Authorization: Bearer <token>"This is the URL of Bot Chief (bot:global/chief).
Paste <base>/v1 as the base URL of an OpenAI client (chat completions, responses, models).Paste <base> as the base URL of an Anthropic client (Messages, models).Paste <base>/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.
{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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}
Section titled “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
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
realm | path | string | Yes | The realm. |
bot | path | string | Yes | The bot. |
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 <token>" \ -H "Idempotency-Key: req-2026-10-08-001" \ -d '{ "model": "bot:global/chief", "messages": [ { "role": "user", "content": "What is the status of the release?" } ] }'{ "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).
{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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}
Section titled “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
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
realm | path | string | Yes | The realm. |
bot | path | string | Yes | The bot. |
door | path | string | Yes | The door. |
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 <token>" \ -H "Idempotency-Key: req-2026-10-08-002" \ -d '{ "model": "bot:global/chief", "messages": [ { "role": "user", "content": "What is the status of the release?" } ] }'{ "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).
{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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}
Section titled “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
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
realm | path | string | Yes | The realm. |
bot | path | string | Yes | The bot. |
curl -X DELETE "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/global/chief/mcp" \ -H "Authorization: Bearer <token>"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.
{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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}
Section titled “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
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
realm | path | string | Yes | The realm. |
bot | path | string | Yes | The bot. |
door | path | string | Yes | The door. |
curl -X DELETE "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/bots/global/chief/mcp" \ -H "Authorization: Bearer <token>"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.
{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “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:<realm>/<id> (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
Section titled “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
Section titled “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. |
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/compat/anthropic/v1/models" \ -H "x-api-key: <token>"{ "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"}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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}
Section titled “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
Section titled “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. |
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: <token>"{ "type": "model", "id": "bot:global/chief", "display_name": "Chief", "created_at": "2026-10-08T10:30:00Z"}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “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
Section titled “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. |
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: <token>" \ -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?" } ] }'{ "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 }}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “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:<realm>/<id> (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
Section titled “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
Section titled “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. |
curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/compat/openai/v1/models" \ -H "Authorization: Bearer <token>"{ "object": "list", "data": [ { "id": "bot:global/chief", "object": "model", "created": 1762533000, "owned_by": "hoody" } ]}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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}
Section titled “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
Section titled “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. |
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 <token>"{ "id": "bot:global/chief", "object": "model", "created": 1762533000, "owned_by": "hoody"}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “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
Section titled “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. |
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 <token>" \ -H "Idempotency-Key: req-2026-10-08-004" \ -d '{ "model": "bot:global/chief", "messages": [ { "role": "user", "content": "What is the status of the release?" } ] }'{ "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 }}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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
Section titled “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
Section titled “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. |
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 <token>" \ -H "Idempotency-Key: req-2026-10-08-005" \ -d '{ "model": "bot:global/chief", "input": "What is the status of the release?" }'{ "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 }}{ "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. |
{ "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 …. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |
{ "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. |