Agent: Todos
Section titled “Agent: Todos”File, read, update, claim, run, snooze, archive, and purge agent todos. Approve or deny run proposals, post comments, run an LLM triage pass over the inbox, and manage the lease lifecycle. The agent routes live under /api/v1/agent/todos on the agent service inside a container.
List and read
Section titled “List and read”GET /api/v1/agent/todos
Section titled “GET /api/v1/agent/todos”List todos. Lists master todos for the requesting cwd / config-dir, with the store-wide poll-cursor revision (a change-detection cursor for the whole list, NOT the per-todo CAS token; todos.update, todos.archive, todos.snooze, and todos.claim take each todo’s own revision from client.agent.todos.get()). Filter with the query parameters states, tags (repeat the parameter for several values), query, open_only, and all. An invalid boolean or unknown state is a 400. The result is open todos first, then by priority, rank, and number (the default order), or closed todos by closed_at newest first when sort=closed_at.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
states | query | array | No | Only todos in one of these states. Repeat the parameter for several (?states=ready&states=blocked). |
tags | query | array | No | Only todos carrying these tags. Repeat the parameter for several (?tags=a&tags=b). |
query | query | string | No | Free-text filter over title and body. |
open_only | query | boolean | No | When true, only open (non-terminal) todos. |
all | query | boolean | No | When true, include archived and closed todos. |
sort | query | string | No | List order. default (or omitted): open todos first, then priority, rank, number. closed_at: closed todos by closed_at, newest first; todos without a closed_at follow in the default order. Any other value is 400. |
page | query | integer | No | 1-based page number for pagination. |
limit | query | integer | No | Maximum items per page (0 = no pagination). |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
realm | query | string | No | The realm to list: “global” (todos not tied to a realm), a 24-hex realm id, or “all” for every realm this login serves. Also accepted as the X-Hoody-Realm header, except “all”. Omitted, the agent’s current realm. |
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. |
curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos?open_only=true&tags=ops&page=1&limit=20' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -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.todos.listIterator({ open_only: true, tags: ['ops'], page: 1, limit: 20 })) { console.log(item);}{ "items": [ { "id": "todo_3f9a1c7e5b2d4086a1c3e5f7", "num": 14, "title": "Rotate the staging credentials", "state": "ready", "priority": 1, "rank": 14, "revision": 8, "cwd": "/home/user/projects/my-app", "source": { "kind": "manual" }, "tags": ["ops"], "automation": "inherit", "created_at": "2026-01-12T14:22:05Z", "updated_at": "2026-01-13T09:00:00Z" } ], "meta": { "total": 1 }, "revision": 42}{ "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) | The request did not come through the program’s URL. 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": "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. | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "code": "restriction_unknown", "message": "the login's realm restriction could not be read — retry"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
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/todos/{id}
Section titled “GET /api/v1/agent/todos/{id}”Read a todo. Returns the full todo record including its timeline and any pending proposals.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -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.todos.get('todo_3f9a1c7e5b2d4086a1c3e5f7');{ "status": "ok", "todo": { "id": "todo_3f9a1c7e5b2d4086a1c3e5f7", "num": 14, "title": "Rotate the staging credentials", "state": "ready", "priority": 1, "rank": 14, "revision": 8, "cwd": "/home/user/projects/my-app", "source": { "kind": "manual" }, "tags": ["ops"], "automation": "inherit", "created_at": "2026-01-12T14:22:05Z", "updated_at": "2026-01-13T09:00: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) | The request did not come through the program’s URL. 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": "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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/todos/revision
Section titled “GET /api/v1/agent/todos/revision”Get the todos store revision. Returns the store-wide revision counter, a cheap poll cursor that moves on every persisted mutation. Use it for change detection over the whole list. It is NOT the per-todo CAS token used by updates.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
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. 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. |
curl -X GET 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/revision' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -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.todos.getRevision();{ "revision": 42}{ "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) | The request did not come through the program’s URL. 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": "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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "code": "restriction_unknown", "message": "the login's realm restriction could not be read — retry"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
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
Section titled “Create”POST /api/v1/agent/todos
Section titled “POST /api/v1/agent/todos”File a todo. Files a new master todo. Deterministic triage (normalize, fingerprint-dedupe, defaults) runs server-side. The todo’s working directory is taken from the body cwd, or from the X-Hoody-Cwd request-scope header when the body field is omitted; one of the two is required.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. Required when the body omits cwd. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Todo title. |
body | string | No | Todo body / description. |
priority | integer | No | Optional priority band 0..4 (0 = P0 urgent, 4 = P4 someday); defaults to 2 when omitted. Must be a JSON integer in range; a string or out-of-range value is rejected. |
tags | array of strings | No | Optional tags. |
cwd | string | No | The todo’s working directory. Defaults to the X-Hoody-Cwd request-scope header when omitted; one of the two must be set. |
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{ "title": "Rotate the staging credentials", "body": "Swap the staging IAM access keys this week.", "priority": 1, "tags": ["ops"] }'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.todos.create({ title: 'Rotate the staging credentials', body: 'Swap the staging IAM access keys this week.', priority: 1, tags: ['ops'],});{ "status": "ok", "todo": { "automation": "inherit", "created_at": "2026-01-12T14:22:05Z", "cwd": "/home/user/projects/my-app", "id": "todo_3f9a1c7e5b2d4086a1c3e5f7", "num": 14, "priority": 1, "rank": 14, "revision": 8, "source": { "kind": "manual" }, "state": "ready", "tags": ["ops"], "title": "Rotate the staging credentials", "updated_at": "2026-01-13T09:00: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) | The request did not come through the program’s URL. 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": "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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. |
Update
Section titled “Update”PATCH /api/v1/agent/todos/{id}
Section titled “PATCH /api/v1/agent/todos/{id}”Update a todo (CAS). Applies a CAS-guarded field patch or state transition to a todo. Pass the current revision; a stale revision is rejected (the 409 todo_conflict path). Only the keys present in the body are patched.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
revision | integer | Yes | The todo’s own current revision (from client.agent.todos.get(), NOT the store-wide revision). A stale value is rejected. |
title | string | No | New title. |
body | string | No | New body. |
state | string | No | New state transition. |
priority | integer | No | New priority. |
rank | integer | No | New ordering rank. |
tags | array of strings | No | New tag set. |
cwd | string | No | Retarget the todo’s working directory. |
curl -X PATCH 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{ "revision": 8, "priority": 0, "state": "ready" }'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.todos.update('todo_3f9a1c7e5b2d4086a1c3e5f7', { revision: 8, priority: 0, state: 'ready',});{ "status": "ok", "todo": { "id": "todo_3f9a1c7e5b2d4086a1c3e5f7", "num": 14, "title": "Rotate the staging credentials", "state": "ready", "priority": 0, "rank": 14, "revision": 9, "cwd": "/home/user/projects/my-app", "source": { "kind": "manual" }, "tags": ["ops"], "automation": "inherit", "created_at": "2026-01-12T14:22:05Z", "updated_at": "2026-01-13T09:30:00Z" }}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request or refused update | The body is malformed (revision missing, a priority outside 0..4, an unknown automation level, a wake_at that is not RFC3339), or the update cannot be applied: a state that is not a todo state, a move to in_progress, a move the lifecycle does not allow, a state change while a worker holds the todo’s lease, or an archived todo. Nothing is written. | Fix the body; for a state change, pick a state the todo’s current state allows, end a running worker first, and use claimTodo or runTodo to start work. |
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) | The request did not come through the program’s URL. 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": "todo_conflict", "message": "todo_conflict: revision mismatch (refetch and retry)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
todo_conflict | Todo revision conflict | The supplied revision is stale (a concurrent update advanced the todo). | GET the todo (client.agent.todos.get()) for its current revision, the per-todo CAS token, NOT the store-wide todos.getRevision, and re-issue the update. |
{ "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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. |
Lifecycle transitions
Section titled “Lifecycle transitions”POST /api/v1/agent/todos/{id}/archive
Section titled “POST /api/v1/agent/todos/{id}/archive”Archive a todo. Marks the todo as archived. Pass the current revision; a stale revision is rejected.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
revision | integer | Yes | The todo’s own current revision (from client.agent.todos.get()). Required; a stale or absent value is rejected. |
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7/archive' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{ "revision": 8 }'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.todos.archive('todo_3f9a1c7e5b2d4086a1c3e5f7', { revision: 8 });{ "status": "ok", "todo": { "automation": "inherit", "created_at": "2026-01-12T14:22:05Z", "cwd": "/home/user/projects/my-app", "id": "todo_3f9a1c7e5b2d4086a1c3e5f7", "num": 14, "priority": 1, "rank": 14, "revision": 8, "source": { "kind": "manual" }, "state": "ready", "tags": ["ops"], "title": "Rotate the staging credentials", "updated_at": "2026-01-13T09:00: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) | The request did not come through the program’s URL. 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": "todo_conflict", "message": "todo_conflict: revision mismatch (refetch and retry)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
todo_conflict | Todo revision conflict | The supplied revision is stale (a concurrent update advanced the todo). | GET the todo (client.agent.todos.get()) for its current revision, the per-todo CAS token, NOT the store-wide todos.getRevision, and re-issue the update. |
{ "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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/todos/{id}/snooze
Section titled “POST /api/v1/agent/todos/{id}/snooze”Snooze a todo. Snoozes a todo until a wake time. Pass an empty string in wake_at to clear an existing snooze.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
wake_at | string | Yes | Wake time, RFC3339 (for example 2026-07-04T09:00:00Z); an empty string clears the snooze. |
revision | integer | Yes | The todo’s own current revision (from client.agent.todos.get()); a stale value is rejected. |
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7/snooze' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{ "wake_at": "2026-07-04T09:00:00Z", "revision": 8 }'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.todos.snooze('todo_3f9a1c7e5b2d4086a1c3e5f7', { wake_at: '2026-07-04T09:00:00Z', revision: 8,});{ "status": "ok", "todo": { "automation": "inherit", "created_at": "2026-01-12T14:22:05Z", "cwd": "/home/user/projects/my-app", "id": "todo_3f9a1c7e5b2d4086a1c3e5f7", "num": 14, "priority": 1, "rank": 14, "revision": 8, "source": { "kind": "manual" }, "state": "ready", "tags": ["ops"], "title": "Rotate the staging credentials", "updated_at": "2026-01-13T09:00: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) | The request did not come through the program’s URL. 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": "todo_conflict", "message": "todo_conflict: revision mismatch (refetch and retry)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
todo_conflict | Todo revision conflict | The supplied revision is stale (a concurrent update advanced the todo). | GET the todo (client.agent.todos.get()) for its current revision, the per-todo CAS token, NOT the store-wide todos.getRevision, and re-issue the update. |
{ "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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. |
Lease and runs
Section titled “Lease and runs”POST /api/v1/agent/todos/{id}/claim
Section titled “POST /api/v1/agent/todos/{id}/claim”Claim a todo. Takes the execution lease on a todo and moves it to in_progress. session_id is the lease owner and is required: it is the identity every later guard compares against, so the same value must come back on todos.release, and a second caller claiming under a different session_id is refused while the lease is live. Pass the todo’s own revision for a fresh claim; the lease’s current owner may re-claim without one to refresh the TTL idempotently. run_id is free-form correlation the lease carries for you. The lease belongs to the daemon that minted it; another daemon’s live lease is never taken over.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | Identity that will OWN the lease; required and non-empty. todos.release must present the same value; while the lease is live another session_id cannot claim the todo. |
run_id | string | No | Optional correlation id recorded on the lease (for example the run this claim belongs to); not an identity, it is never used for ownership checks. |
revision | integer | No | The todo’s own current revision (from client.agent.todos.get()). Conditionally required: a fresh claim (no live lease, or a lease held by another session_id) must carry it; omitted means revision 0 and the CAS refuses the claim (409 todo_conflict). The lease’s current owner re-claiming to refresh its TTL may omit it. |
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7/claim' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{ "session_id": "sess_01HMZXEXAMPLE", "run_id": "run_01HMZXEXAMPLE", "revision": 8 }'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.todos.claim('todo_3f9a1c7e5b2d4086a1c3e5f7', { session_id: 'sess_01HMZXEXAMPLE', run_id: 'run_01HMZXEXAMPLE', revision: 8,});{ "status": "ok", "todo": { "automation": "inherit", "created_at": "2026-01-12T14:22:05Z", "cwd": "/home/user/projects/my-app", "id": "todo_3f9a1c7e5b2d4086a1c3e5f7", "num": 14, "priority": 1, "rank": 14, "revision": 8, "source": { "kind": "manual" }, "state": "ready", "tags": ["ops"], "title": "Rotate the staging credentials", "updated_at": "2026-01-13T09:00: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) | The request did not come through the program’s URL. 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": "todo_conflict", "message": "todo_conflict: revision mismatch (refetch and retry)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
todo_conflict | Todo revision conflict | The supplied revision is stale (a concurrent update advanced the todo). | GET the todo (client.agent.todos.get()) for its current revision, the per-todo CAS token, NOT the store-wide todos.getRevision, and re-issue the update. |
{ "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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/todos/{id}/release
Section titled “POST /api/v1/agent/todos/{id}/release”Release a todo. Ends the run holding a todo’s lease and records the outcome. session_id must be the same value todos.claim leased under; the daemon refuses a release from any other session, and refuses one on a todo with no active lease. outcome is required and decides the resulting state: done moves the todo to review, blocked moves it to blocked, failed moves it to ready and counts against the attempt cap, cancelled moves it to ready without counting an attempt (so a closed or abandoned run never disqualifies the todo). summary and question_for_human are appended to the timeline and are capped at 1024 characters each.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | The session that owns the lease; required, and must match the value todos.claim leased under. A mismatch is refused. |
outcome | string | Yes | How the run ended. One of done, blocked, failed, cancelled. done moves the todo to review; blocked to blocked; failed to ready and increments the attempt counter; cancelled to ready without incrementing it. Required; any other value is rejected. |
summary | string | No | Short account of what the run did, written to the timeline as the run_end entry (capped at 1024 characters). |
question_for_human | string | No | A question to surface to the operator alongside the outcome; the field a blocked run uses to say what it needs (capped at 1024 characters). |
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7/release' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{ "session_id": "sess_01HMZXEXAMPLE", "outcome": "done", "summary": "Rotated the staging IAM keys and verified the deploy." }'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.todos.release('todo_3f9a1c7e5b2d4086a1c3e5f7', { session_id: 'sess_01HMZXEXAMPLE', outcome: 'done', summary: 'Rotated the staging IAM keys and verified the deploy.',});{ "status": "ok", "todo": { "automation": "inherit", "created_at": "2026-01-12T14:22:05Z", "cwd": "/home/user/projects/my-app", "id": "todo_3f9a1c7e5b2d4086a1c3e5f7", "num": 14, "priority": 1, "rank": 14, "revision": 8, "source": { "kind": "manual" }, "state": "ready", "tags": ["ops"], "title": "Rotate the staging credentials", "updated_at": "2026-01-13T09:00: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) | The request did not come through the program’s URL. 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": "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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/todos/{id}/run
Section titled “POST /api/v1/agent/todos/{id}/run”Run a todo’s orchestrator. Dispatches a background worker to autonomously work a todo and returns {job_id, session_id}. The request itself counts as the human approval and no further confirmation is asked; the model-facing run_todo tool, by contrast, refuses an autonomous caller. The minted job completes at DISPATCH (it carries the worker session_id for correlation, but the resident worker runs asynchronously and is NOT tracked by the job lifecycle): a succeeded job means the run was started, not that the todo is finished. Poll the todo itself (client.agent.todos.get()); it lands in review when the worker is done.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”The body is an optional JSON object. Every _-prefixed key is ignored (removed before dispatch), the request scope (cwd / config-dir) is added from the scope headers, and all other keys are passed through verbatim. The todo {id} comes from the path.
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7/run' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{}'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.todos.start('todo_3f9a1c7e5b2d4086a1c3e5f7', {});{ "job_id": "job_01HMZXEXAMPLE", "session_id": "sess_01HMZXEXAMPLE"}{ "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": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The daemon’s own administrator check refused the request. When the daemon was started with its own administrator token, it refuses these operations because an HTTP request cannot supply that token. In the standard HTTP deployment the daemon has no such token and this code is not returned. | Run the daemon in the standard HTTP deployment (--auth-token-path, no separate administrator token) so its check allows HTTP requests, or perform the operation from the agent’s terminal interface inside the container. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. 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": "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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/todos/{id}/cancel-run
Section titled “POST /api/v1/agent/todos/{id}/cancel-run”Cancel a todo’s run. Cancels an in-flight orchestrator run for a todo.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”No body fields are read; the todo {id} comes from the path. An optional JSON object is accepted.
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7/cancel-run' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{}'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.todos.cancel('todo_3f9a1c7e5b2d4086a1c3e5f7', {});{ "status": "ok"}{ "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) | The request did not come through the program’s URL. 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": "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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. |
Proposals
Section titled “Proposals”POST /api/v1/agent/todos/{id}/proposals/{pid}/approve
Section titled “POST /api/v1/agent/todos/{id}/proposals/{pid}/approve”Approve a todo proposal. Approves a run proposal on a todo. Approval is NOT inert: the daemon starts the approved proposal as a background worker session equivalent to client.agent.todos.start(), an autonomous run that spends model budget and asks for no human confirmation. The paired todos.denyProposal spawns no worker.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
pid | path | string | Yes | Proposal identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”No body fields are read; the todo {id} and proposal {pid} come from the path. An optional JSON object is accepted.
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7/proposals/prop_01HMZXEXAMPLE/approve' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{}'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.todos.approveProposal( 'todo_3f9a1c7e5b2d4086a1c3e5f7', 'prop_01HMZXEXAMPLE', {},);{ "status": "ok"}{ "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": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The daemon’s own administrator check refused the request. When the daemon was started with its own administrator token, it refuses these operations because an HTTP request cannot supply that token. In the standard HTTP deployment the daemon has no such token and this code is not returned. | Run the daemon in the standard HTTP deployment (--auth-token-path, no separate administrator token) so its check allows HTTP requests, or perform the operation from the agent’s terminal interface inside the container. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. 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": "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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/todos/{id}/proposals/{pid}/deny
Section titled “POST /api/v1/agent/todos/{id}/proposals/{pid}/deny”Deny a todo proposal. Denies a run proposal on a todo. No worker is spawned.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
pid | path | string | Yes | Proposal identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”No body fields are read; the todo {id} and proposal {pid} come from the path. An optional JSON object is accepted.
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7/proposals/prop_01HMZXEXAMPLE/deny' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{}'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.todos.denyProposal( 'todo_3f9a1c7e5b2d4086a1c3e5f7', 'prop_01HMZXEXAMPLE', {},);{ "status": "ok"}{ "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) | The request did not come through the program’s URL. 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": "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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. |
Conversation
Section titled “Conversation”POST /api/v1/agent/todos/{id}/message
Section titled “POST /api/v1/agent/todos/{id}/message”Comment + run an orchestrator turn. Posts a comment AND kicks an orchestrator turn on the todo. Returns {job_id}, but the job completes at DISPATCH: the orchestrator runs as a daemon-managed resident whose turn is NOT tracked by the job lifecycle. Poll the todo itself (client.agent.todos.get()) for the orchestrator’s reply or proposal on the timeline; a succeeded job means the turn was dispatched, not finished.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | The message that both comments and prompts the orchestrator. |
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7/message' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{ "text": "Please rotate the staging IAM keys today and confirm." }'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.todos.sendMessage('todo_3f9a1c7e5b2d4086a1c3e5f7', { text: 'Please rotate the staging IAM keys today and confirm.',});{ "job_id": "job_01HMZXEXAMPLE"}{ "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) | The request did not come through the program’s URL. 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": "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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/todos/{id}/messages
Section titled “POST /api/v1/agent/todos/{id}/messages”Comment on a todo. Posts a comment onto a todo’s timeline WITHOUT triggering an orchestrator turn. Use /message (singular) to also kick an orchestrator turn.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Todo identifier. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | The comment body to append to the todo timeline. |
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/todo_3f9a1c7e5b2d4086a1c3e5f7/messages' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{ "text": "FYI: the new key rotation policy takes effect Monday." }'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.todos.createComment('todo_3f9a1c7e5b2d4086a1c3e5f7', { text: 'FYI: the new key rotation policy takes effect Monday.',});{ "status": "ok"}{ "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) | The request did not come through the program’s URL. 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": "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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/todos/purge
Section titled “POST /api/v1/agent/todos/purge”Purge archived todos. Permanently and irreversibly removes archived todos; the tombstones are destroyed at once, with no confirmation step. The optional body filters which archived todos to purge; an empty body removes archived todos older than the 90-day retention age across every realm, keeping more recently archived ones.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”The body is an optional JSON object. Every _-prefixed key is ignored (removed before dispatch), the request scope (cwd / config-dir) is added from the scope headers, and all other keys are passed through verbatim. With an empty body, archived todos older than the 90-day retention age are removed across every realm.
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/purge' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{}'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.todos.purgeArchived({});{ "status": "ok"}{ "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": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The daemon’s own administrator check refused the request. When the daemon was started with its own administrator token, it refuses these operations because an HTTP request cannot supply that token. In the standard HTTP deployment the daemon has no such token and this code is not returned. | Run the daemon in the standard HTTP deployment (--auth-token-path, no separate administrator token) so its check allows HTTP requests, or perform the operation from the agent’s terminal interface inside the container. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. 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": "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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/todos/triage
Section titled “POST /api/v1/agent/todos/triage”Run an LLM triage pass. Kicks an LLM triage pass over the inbox and returns {job_id}. It spends model budget on an autonomous LLM run and asks for no human confirmation, the same property that makes client.agent.todos.start() a privileged operation. With an empty body the whole inbox is triaged.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
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. 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. |
Request body
Section titled “Request body”The body is an optional JSON object. Every _-prefixed key is ignored (removed before dispatch), the request scope (cwd / config-dir) is added from the scope headers, and all other keys are passed through verbatim. Optional triage scoping; an empty body triages the whole inbox.
curl -X POST 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/todos/triage' \ -H 'X-Hoody-Cwd: /home/user/projects/my-app' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <token>' \ --data '{}'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.todos.triage({});{ "job_id": "job_01HMZXEXAMPLE"}{ "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": "admin_unauthorized", "message": "memory administration requires a valid admin token"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
admin_unauthorized | Rejected by the daemon’s own admin gate | The daemon’s own administrator check refused the request. When the daemon was started with its own administrator token, it refuses these operations because an HTTP request cannot supply that token. In the standard HTTP deployment the daemon has no such token and this code is not returned. | Run the daemon in the standard HTTP deployment (--auth-token-path, no separate administrator token) so its check allows HTTP requests, or perform the operation from the agent’s terminal interface inside the container. |
forbidden | Forbidden (Source IP Guard) | The request did not come through the program’s URL. 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": "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). | 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. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked, or written. | Retry once; if it persists, repair the named store inside the container. Inspect the agent’s log (GET /logs) for a “store failure” entry. |
{ "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. |