Agent: Discovery
Section titled “Agent: Discovery”The agent’s shell, file and search tools act on containers through the agent’s own sign-in to a Hoody account. A newly created container’s agent has none, so those tools answer “not logged in” until the agent signs in. This page covers the operations that sign the agent in to a Hoody account, switch its active realm, and list the realms and containers a session can bind to.
Begin with the sign-in operations in this order:
GET /api/v1/agent/hoody/auth/statusto check whether the agent already holds an active credential (auth_active: truemeans it is).POST /api/v1/agent/hoody/auth/bootstrapto install a credential from a platform token if none is active, or replace one owned by the same account.PUT /api/v1/agent/hoody/realmto choose the realm the agent will work in.
Then use the discovery operations:
GET /api/v1/agent/realmsto list the realms visible to that login.GET /api/v1/agent/containersto list the containers in a realm.
Sign-in
Section titled “Sign-in”GET /api/v1/agent/hoody/auth/status
Section titled “GET /api/v1/agent/hoody/auth/status”Reports which Hoody account the agent is logged in as and which realm it is scoped to, without opening a session. The reply is secret-free: no token, no email, no expiry.
auth_active: true means a platform credential is active; auth_active: false means none is (every local session is visible). owner_fingerprint is the session-ownership view: every session this agent creates reports it as started.owner_fingerprint, and sessions are readable only while the same account is active. restricted and realm_ids describe a realm-limited login (see restriction, restriction_reason). realm is the scope session listing and by-id reads use: the agent’s pinned realm when it has one, otherwise its active realm selection (an empty id means no realm is selected).
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.whoami();curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/hoody/auth/status" \ -H "Authorization: Bearer $HOODY_TOKEN"{ "connected": true, "source": "stored", "account_id": "67e89abc123def456789abcd", "username": "alex", "alias": "alex@acme.example", "auth_active": true, "owner_fingerprint": "8c3a9f1b6e2d4a7c", "realm_isolation": "unknown", "restricted": false, "restriction": "none", "realm": { "id": "", "pinned": false }}{ "code": "realm_scope_unsupported", "message": "realm scope not supported on this route"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
{ "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. |
{ "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. |
POST /api/v1/agent/hoody/auth/bootstrap
Section titled “POST /api/v1/agent/hoody/auth/bootstrap”Installs the Hoody platform credential from a raw token. This is the programmatic equivalent of an interactive login.
The submitted token must meet every requirement below, or it is refused:
- Belong to the same account as the container’s owner (the token is checked against this container on one of the token’s realms, or on the account for an all-realms token).
- Be created from a signed-in session, not from another token. A session login (the account’s JWT) is not an API token and is refused the same way.
- Carry the full login grant: vault access, the container, project and resource permissions the agent uses, and permission to create tokens.
- Have at least 15 minutes of life left.
- Not belong to a platform administrator.
A refusal names what the token lacks: login_not_for_this_box (the token cannot see this container), login_grant_insufficient (with details.missing listing what it lacks), login_expiring, admin_login_refused, or token_rejected (invalid or expired). The token itself is write-only and is never returned.
In the TypeScript SDK this operation has two methods:
client.agent.signIn({ token })signs this container’s agent in to the Hoody platform with a token of the box’s owner;tokenis required. Until then the agent’s shell and file tools answer “not logged in”.client.agent.platform.bootstrapToken(data)sends the same request body as given, without the SDK checking fortokenfirst.
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
token | string | Yes | The raw Hoody platform token to install. Write-only; validated via the sidecar before install and never echoed. |
capability | string | No | The operator bootstrap capability, required only on deployments configured with one; a mismatch is answered 404. |
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.signIn({ token: 'hdy_...' });curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/hoody/auth/bootstrap" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"token": "hdy_..."}'{ "status": "ok", "outcome": "renewed", "connected": true, "account_id": "67e89abc123def456789abcd", "username": "alex", "alias": "alex@acme.example", "restricted": false}{ "status": "ok", "outcome": "installed", "connected": true, "account_id": "67e89abc123def456789abcd", "username": "alex", "alias": "alex@acme.example", "restricted": false}{ "code": "token_rejected", "message": "token rejected"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
token_rejected | Token rejected | The submitted platform token failed sidecar validation (invalid or expired). The message is a fixed public string; no sidecar detail is echoed. | Supply a valid platform token (obtain one from the platform auth API). |
{ "code": "login_grant_insufficient", "message": "the login token cannot create this agent's tokens; it lacks: permissions.resources.create_tokens", "details": { "missing": ["permissions.resources.create_tokens"] }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
login_not_for_this_box | Token cannot see this box | The token cannot see the container this agent runs in (on any of its realms, or on the account for an all-realms token), or the agent does not know its own container id. Nothing was installed. | Use a token whose realms include this container, or add the container to one of the token’s realms. |
login_grant_insufficient | Token lacks the login grant | The token cannot create the tokens the agent needs: it was created from another token, lacks vault access, or lacks some of the container, project and resource permissions the agent uses or permission to create tokens. details.missing lists what it lacks. A session login (a JWT, not an API token) is refused with this code and no list. Nothing was installed. | Create a token from a signed-in session with the listed permissions, then retry. |
login_expiring | Token expires too soon | The token expires within 15 minutes, too soon for the agent to create usable tokens from it. Nothing was installed. | Create a longer-lived token, then retry. |
admin_login_refused | Administrator token refused | The token belongs to a platform administrator. This operation installs only a token of the box’s owner. Nothing was installed. | Use a token of the account that owns this box. |
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": "credential_present", "message": "a platform credential is already active"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
credential_present | Credential already present | A Hoody platform credential this operation cannot replace is active: the HOODY_TOKEN environment variable, or a stored login whose account could not be identified. This operation never logs a credential out. | For a stored login, log out from the agent’s terminal interface inside the container (its login screen), then retry. For the HOODY_TOKEN environment variable, logging out does not clear it; remove HOODY_TOKEN from the agent’s launch environment and restart the agent, then retry. |
agent_login_conflict | Logged in to a different account | The agent is logged in to a different account than the supplied token belongs to. This operation replaces a login only with a token for the same account; nothing was changed. | Supply a token for the account the agent is logged in to, or log out from the agent’s terminal interface inside the container first. |
bootstrap_in_progress | Bootstrap in progress | Another bootstrap install is already running. The operation is single-flight; a concurrent call is refused rather than queued. | Wait for the in-flight install to finish, then retry. |
login_changed | Token changed during install | The token’s account, realms or permissions changed between its check and its install. Nothing was installed. | Retry. |
{ "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 size limit (default 8 MiB). | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "commit_failed", "message": "credential install failed"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
commit_failed | Credential install failed | The token validated but the platform sidecar could not install it. An upstream (sidecar) failure after acceptance. | Retry; if persistent, inspect the daemon logs. |
{ "code": "platform_unavailable", "message": "platform credential state is unreadable"}| 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. |
platform_starting | Platform starting | The platform sidecar is still starting; the install could not complete yet. Retryable. | Retry the request in a moment. |
platform_unavailable | Platform credential state unavailable | The daemon could not read its platform-credential state (a corrupt or unreadable session); the request fails closed rather than resolving an ambiguous credential. | Repair or remove the agent’s unreadable stored platform login inside the container, then retry. |
PUT /api/v1/agent/hoody/realm
Section titled “PUT /api/v1/agent/hoody/realm”Switches the realm the agent is working in, exactly like the realm selector of its terminal interface. The selection is saved and applies to every client; it is the realm new sessions bind to by default and the realm session listing uses on an agent without a pinned realm.
realm_id is a 24-hex realm id, or an empty string for all realms. An agent logged in with a realm-limited token may select only a realm its login serves (GET /api/v1/agent/hoody/auth/status lists them in realm_ids); any other realm, or all realms, is refused 403 realm_not_allowed. While the login’s realms cannot be read yet the switch is refused 503 restriction_unknown.
Request body
Section titled “Request body”| Name | Type | Required | Description |
|---|---|---|---|
realm_id | string | Yes | A 24-hex realm id, or an empty string for all realms. |
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.realms.use({ realm_id: '67e89abc123def456789abcd' });curl -X PUT "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/hoody/realm" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"realm_id": "67e89abc123def456789abcd"}'{ "realm_id": "67e89abc123def456789abcd"}{ "code": "bad_request", "message": "invalid request"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
{ "code": "realm_not_allowed", "message": "realm 0123456789abcdef01234567 is not allowed for this realm-limited login"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
realm_not_allowed | Realm not allowed for this login | The agent is logged in with a token limited to some realms (GET /api/v1/agent/hoody/auth/status lists them in realm_ids), and the requested realm is not one of them, or is all realms. The selection was not changed. | Select a realm the login serves, or log the agent in with a token that covers this realm. |
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 size limit (default 8 MiB). | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "restriction_unknown", "message": "the login's realm restriction could not be read (introspection failed) — retry, or log in again"}| 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 /api/v1/agent/hoody/auth/status reports restriction: "unknown"), so it changes no selection until it can. The selection was not changed. | Retry after the restriction is readable. |
Discovery
Section titled “Discovery”GET /api/v1/agent/realms
Section titled “GET /api/v1/agent/realms”Lists the realms visible to the token, so a caller can pick a valid bind target for POST /api/v1/agent/sessions. The agent’s own realm scope (pinned or active, and whether it is blocked) is on GET /api/v1/agent/hoody/auth/status.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
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: the .hoody project layer, record cwd, or tool and workflow cwd. Required by routes that resolve a cwd (for example POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override selecting which on-disk .hoody install a stateless read or write resolves against. |
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.realms.listIterator()) { // each item is a 24-hex realm id}curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/realms" \ -H "Authorization: Bearer $HOODY_TOKEN"{ "items": [ "67e89abc123def456789abcd", "890abcdef12345678901cdef", "0123456789abcdef01234567" ], "meta": { "total": 3 }}{ "code": "realm_scope_unsupported", "message": "realm scope not supported on this route"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
{ "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": "not_logged_in", "message": "this agent is not logged in to Hoody; log it in from the agent page, then retry"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_logged_in | Agent not logged in to Hoody | The route needs the agent’s Hoody login and the agent holds none. | Log the agent in to Hoody from the agent page, then retry. |
hoody_auth_expired | Agent’s Hoody login expired | The Hoody platform refused the agent’s login: the token expired or was revoked. | Log the agent in again, then retry. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |
GET /api/v1/agent/containers
Section titled “GET /api/v1/agent/containers”Lists the containers in a realm so a caller can pick a bind target. A 24-hex realm id lists that realm’s containers; an omitted or empty realm, or global, lists the agent’s active realm; any other value is refused.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
page | query | integer | No | 1-based page number for pagination. |
limit | query | integer | No | Maximum items per page (0 = no pagination). |
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 or a 24-hex id. Rejected (400 realm_scope_unsupported) on active-only or no-realm routes. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer, record cwd, or tool and workflow cwd. Required by routes that resolve a cwd (for example POST /todos; client.agent.todos.create() also accepts a body cwd). |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override selecting which on-disk .hoody install a stateless read or write resolves against. |
X-Hoody-Realm | header | string | No | Per-request realm selector: global or a 24-hex id (also accepted as ?realm=). Rejected (400 realm_scope_unsupported) on active-only or no-realm routes. |
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.containers.listIterator({ realm: '67e89abc123def456789abcd' })) { // each item is a container row in the selected realm}curl -X GET "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/containers?realm=67e89abc123def456789abcd" \ -H "Authorization: Bearer $HOODY_TOKEN"{ "items": [ { "id": "67e89abc123def456789abcd-890abcdef12345678901cdef", "name": "acme-prod", "status": "running" }, { "id": "0123456789abcdef01234567-fedcba9876543210abcdef01", "name": "acme-staging", "status": "running" } ], "meta": { "total": 2 }}{ "code": "realm_scope_unsupported", "message": "realm scope not supported on this route"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
bad_request | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. |
invalid_realm | Invalid realm selector | The realm selector is malformed (not ""/“global”/a 24-hex id). | Pass a valid realm selector. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
forbidden | Forbidden (Source IP Guard) | 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": "not_logged_in", "message": "this agent is not logged in to Hoody; log it in from the agent page, then retry"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_logged_in | Agent not logged in to Hoody | The route needs the agent’s Hoody login and the agent holds none. | Log the agent in to Hoody from the agent page, then retry. |
hoody_auth_expired | Agent’s Hoody login expired | The Hoody platform refused the agent’s login: the token expired or was revoked. | Log the agent in again, then retry. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
{ "code": "service_unavailable", "message": "service unavailable"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
service_unavailable | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor Retry-After and retry. |