Agent: Models & Providers
Section titled “Agent: Models & Providers”These endpoints let you browse the agent’s merged model and provider catalogue, manage provider authentication (API keys, OAuth logins, pooled OAuth accounts), tune the Jev decision subsystem, and run tool-free model completions (one shot or streamed). Use them to discover what is bindable, configure credentials, and execute ad-hoc calls against the underlying LLM providers without an active session.
All endpoints are scoped to a single container. The container hostname is {projectId}-{containerId}-agent-1.{server}.containers.hoody.com, with {projectId} and {containerId} each exactly 24 lowercase hexadecimal characters and {server} the server label (for example node-example-1).
Models
Section titled “Models”GET /api/v1/agent/models
Section titled “GET /api/v1/agent/models”List the merged model catalogue: every catalogued provider model (spec, display name, context window, output limit, reasoning flag, display prices) AND every selectable fusion composite. Each entry carries source:"provider" or source:"fusion" so a caller can tell them apart. A composite’s context_window is the minimum across its catalogued members and it carries no per-token price. Only SELECTABLE composites appear; a broken composite is excluded (use client.agent.fusions.list({ include_invalid: true }) to manage those). Fusion composites are cwd/config_dir scoped, so X-Hoody-Cwd and X-Hoody-Config-Dir are honored. A per-request realm header, or X-Hoody-Container (which has no model dimension), returns 400 realm_scope_unsupported.
curl -X GET "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/models?page=1&limit=50" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.agent.models.listIterator({ page: 1, limit: 50 })) { console.log(item);}{ "items": [ { "spec": "anthropic/claude-opus-4-8", "display_name": "Claude Opus 4.8", "source": "provider", "provider": "anthropic", "context_window": 200000, "output_limit": 32000, "reasoning": true } ], "meta": { "total": 1, "page": 1, "limit": 50 }}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 / tool+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/write resolves against. |
Statuses
Section titled “Statuses”{ "items": [], "meta": { "total": 0, "page": 1, "limit": 50 }}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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. |
GET /api/v1/agent/models/{spec}
Section titled “GET /api/v1/agent/models/{spec}”Returns one catalogued model by its full spec (for example anthropic/claude-opus-4-8), OR a fusion composite by its fusion/<slug> spec. The spec itself contains a slash, so pass it URL-encoded or as the trailing path segment. A per-request realm header, or X-Hoody-Container, returns 400 realm_scope_unsupported.
curl -X GET "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/models/anthropic%2Fclaude-opus-4-8" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const model = await client.agent.models.get('anthropic/claude-opus-4-8');console.log(model);{ "spec": "anthropic/claude-opus-4-8", "display_name": "Claude Opus 4.8", "source": "provider", "provider": "anthropic", "context_window": 200000, "output_limit": 32000, "reasoning": true}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
spec | path | string | Yes | The spec. |
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. Required by routes that resolve a cwd. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
Statuses
Section titled “Statuses”{ "spec": "anthropic/claude-opus-4-8", "display_name": "Claude Opus 4.8", "source": "provider", "provider": "anthropic", "context_window": 200000, "output_limit": 32000, "reasoning": true}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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. |
Providers
Section titled “Providers”GET /api/v1/agent/providers
Section titled “GET /api/v1/agent/providers”Lists every catalogued LLM provider (id, display name, model prefix, wire format, model count) from the merged providers.json catalogue. The catalogue is process-wide with no realm dimension, so a per-request realm header returns 400 realm_scope_unsupported.
curl -X GET "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers?page=1&limit=50" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.agent.providers.listIterator({ page: 1, limit: 50 })) { console.log(item);}{ "items": [ { "id": "anthropic", "display_name": "Anthropic", "prefix": "anthropic", "wire_format": "anthropic_messages", "model_count": 12 } ], "meta": { "total": 1, "page": 1, "limit": 50 }}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). |
Statuses
Section titled “Statuses”{ "items": [], "meta": { "total": 0, "page": 1, "limit": 50 }}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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. |
GET /api/v1/agent/providers/{id}
Section titled “GET /api/v1/agent/providers/{id}”Returns one provider’s metadata plus its full catalogued model list.
curl -X GET "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/anthropic" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const provider = await client.agent.providers.get('anthropic');console.log(provider);{ "id": "anthropic", "display_name": "Anthropic", "prefix": "anthropic", "wire_format": "anthropic_messages", "model_count": 12, "models": [ { "spec": "anthropic/claude-opus-4-8", "display_name": "Claude Opus 4.8" } ]}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
Statuses
Section titled “Statuses”{ "id": "anthropic", "display_name": "Anthropic"}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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/providers
Section titled “POST /api/v1/agent/providers”Adds a custom AI provider: an endpoint speaking the OpenAI Chat Completions, OpenAI Responses, or Anthropic Messages API, with its catalogued models. Its models are selectable as <model_prefix>/<model> wherever a model is needed (createSession, PATCH /sessions/{id}/model, agents, Jev) once the provider’s key is stored. Keys never go in this body: store the provider’s key with PUT /providers/{id}/auth/api-key once the provider exists. base_url must be an https URL with no user name, password, query, or fragment, at a public address: an internal address, a name that only resolves inside a network, or a name that resolves to an internal address when a request is made is refused.
To retry a create safely, send an Idempotency-Key header: a retry with the same key and body answers 201 with the provider the first try created; the same key with another body is refused 422 idempotency_key_reused. Without a key, a retry whose first try succeeded answers 409 provider_exists.
Process-wide: a per-request realm, cwd, config-dir, or container header returns 400 realm_scope_unsupported.
curl -X POST "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: <your-key-here>" \ -d '{ "id": "acme", "display_name": "Acme AI", "base_url": "https://api.acme.example/v1", "wire_format": "chat_completions", "models": [ { "model": "acme-large", "context_window": 128000, "output_limit": 8192 } ] }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.create( { id: 'acme', display_name: 'Acme AI', base_url: 'https://api.acme.example/v1', wire_format: 'chat_completions', models: [ { model: 'acme-large', context_window: 128000, output_limit: 8192 } ] }, { idempotencyKey: 'your-key-here' });console.log(result);{ "provider": { "auth_scheme": "bearer", "base_url": "https://api.acme.example/v1", "display_name": "Acme AI", "headers": {}, "id": "acme", "model_prefix": "acme", "models": [ { "context_window": 128000, "display_name": "Acme Large", "model": "acme-large", "output_limit": 8192, "reasoning": false, "spec": "acme/acme-large" } ], "prefix": "acme/", "wire_format": "chat_completions" }}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key | header | string | No | Opaque retry key (1-255 printable ASCII, no whitespace). A retry with the same key and body answers 201 with the provider the first try created; the same key with another body is 422 idempotency_key_reused. Keys are remembered for 24 hours, until the agent restarts. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The provider id: 1-40 lowercase letters, digits, or inner hyphens. It cannot be changed later. |
model_prefix | string | No | The prefix of the provider’s model specs (acme for acme/<model>). It must equal the id; omitted, it is the id. No provider’s id or prefix may already use it, and fusion is reserved. |
display_name | string | No | Display name. Omitted, it is the id. |
wire_format | string | No | The API the endpoint speaks: chat_completions (OpenAI-compatible Chat Completions, the default on create), responses (OpenAI Responses), or messages (Anthropic Messages). |
base_url | string | Yes | The endpoint’s base URL, the part before /chat/completions, /responses, or /v1/messages (for example https://api.acme.example/v1). Must be an https URL with no user name, password, query, or fragment, at a public address. |
auth_scheme | string | No | How the stored key is sent. It follows wire_format: bearer (Authorization: Bearer <key>) with chat_completions and responses, x-api-key with messages. Omitted, it is that value; any other pairing is refused 422 provider_invalid. |
headers | object | No | Static request headers sent on every request, chat_completions only, at most 16. Credential and transport headers (Authorization, x-api-key, api-key, Cookie, Host, Content-Type, Content-Length, Connection, and the other hop-by-hop headers) are refused. Values must not contain ${...}. |
models | array | Yes | The provider’s models, 1 to 500, each selectable as <model_prefix>/<model>. |
Each item in models is an object with:
| Name | Type | Required | Description |
|---|---|---|---|
model | string | Yes | The model id the endpoint expects, as sent upstream (for example llama-3.3-70b). The model’s spec is <model_prefix>/<model>. |
context_window | integer | No | Input context window in tokens. 0 or omitted means unknown, which also turns automatic compaction off for the model. |
output_limit | integer | No | Maximum output tokens. 0 or omitted means unknown. |
reasoning | boolean | No | Marks a reasoning-capable model in the catalogue. Default false. |
Statuses
Section titled “Statuses”{ "provider": { "auth_scheme": "bearer", "base_url": "https://api.acme.example/v1", "display_name": "Acme AI", "headers": {}, "id": "acme", "model_prefix": "acme", "models": [ { "context_window": 128000, "display_name": "Acme Large", "model": "acme-large", "output_limit": 8192, "reasoning": false, "spec": "acme/acme-large" } ], "prefix": "acme/", "wire_format": "chat_completions" }}| Error Code | Title | Description | Resolution |
|---|
{ "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. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| 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), 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": "provider_exists", "message": "a provider with id \"acme\" already exists", "details": { "field": "id" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
provider_exists | Provider exists | A provider with this id already exists, built in or custom. A retried create whose first try succeeded answers this too. | Pick another id, or change the existing custom provider with PATCH /providers/{id}. |
model_prefix_taken | Model prefix taken | Another provider’s models already use this model_prefix. Model specs name their provider by prefix, so two providers cannot share one. | Pick another model_prefix. |
{ "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). A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "provider_invalid", "message": "base_url must use https", "details": { "field": "base_url" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
provider_invalid | Provider definition refused | The definition breaks a rule: an id outside the grammar, a model_prefix that differs from the id, an auth_scheme that does not match the wire_format, a base_url that is not public https, an unknown wire_format or auth_scheme, a refused header, or a model list that is empty, too long, or has duplicates. details.field names the member. Nothing was changed. | Fix the member details.field names and send the request again. |
idempotency_key_reused | Idempotency key reused | The Idempotency-Key was already used for a different provider definition. Nothing was changed. | Send a new key with a new definition, or resend the original definition with this key. |
{ "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, so the operation did not complete. The message names the store. A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log records a “store failure” entry with the store, the failure kind, and a path-free cause. |
{ "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. |
PATCH /api/v1/agent/providers/{id}
Section titled “PATCH /api/v1/agent/providers/{id}”Changes the members the body lists, leaving omitted members as they are. models and headers replace the current list and map. id and model_prefix are fixed: a body that changes model_prefix is refused 400 provider_field_immutable. Built-in providers cannot be changed (409 provider_builtin), nor can a custom provider defined in a project’s providers file (409 provider_not_managed). The stored key is kept. The change applies at once, with no restart: new sessions, model switches, and Jev see it, and every open session using one of the provider’s models picks it up before its next turn.
base_url must be an https URL with no user name, password, query, or fragment, at a public address.
Process-wide: a per-request realm, cwd, config-dir, or container header returns 400 realm_scope_unsupported.
curl -X PATCH "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/acme" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "base_url": "https://eu.api.acme.example/v1" }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.update('acme', { base_url: 'https://eu.api.acme.example/v1'});console.log(result);{ "provider": { "auth_scheme": "bearer", "base_url": "https://api.acme.example/v1", "display_name": "Acme AI", "headers": {}, "id": "acme", "model_prefix": "acme", "models": [ { "context_window": 128000, "display_name": "Acme Large", "model": "acme-large", "output_limit": 8192, "reasoning": false, "spec": "acme/acme-large" } ], "prefix": "acme/", "wire_format": "chat_completions" }}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
model_prefix | string | No | Fixed at creation. Accepted only when equal to the current prefix; any other value is refused 400 provider_field_immutable. |
display_name | string | No | Display name. |
wire_format | string | No | The API the endpoint speaks: chat_completions, responses, or messages. |
base_url | string | No | The endpoint’s base URL. Must be an https URL with no user name, password, query, or fragment, at a public address. |
auth_scheme | string | No | How the stored key is sent. It follows wire_format: bearer with chat_completions and responses, x-api-key with messages. Any other pairing is refused 422 provider_invalid. |
headers | object | No | Static request headers sent on every request, chat_completions only, at most 16. Credential and transport headers are refused. Values must not contain ${...}. On PATCH the object replaces the current headers; {} or null clears them. |
models | array | No | The provider’s models, 1 to 500, each selectable as <model_prefix>/<model>. On PATCH the list replaces the current one. |
Each item in models is an object with:
| Name | Type | Required | Description |
|---|---|---|---|
model | string | Yes | The model id the endpoint expects, as sent upstream. The model’s spec is <model_prefix>/<model>. |
context_window | integer | No | Input context window in tokens. 0 or omitted means unknown. |
output_limit | integer | No | Maximum output tokens. 0 or omitted means unknown. |
reasoning | boolean | No | Marks a reasoning-capable model. Default false. |
Statuses
Section titled “Statuses”{ "provider": { "auth_scheme": "bearer", "base_url": "https://api.acme.example/v1", "display_name": "Acme AI", "headers": {}, "id": "acme", "model_prefix": "acme", "models": [ { "context_window": 128000, "display_name": "Acme Large", "model": "acme-large", "output_limit": 8192, "reasoning": false, "spec": "acme/acme-large" } ], "prefix": "acme/", "wire_format": "chat_completions" }}| Error Code | Title | Description | Resolution |
|---|
{ "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. |
provider_field_immutable | Field cannot be changed | The body changes model_prefix. Model specs, sessions, and agents name the provider by its prefix, so it is fixed when the provider is created. Nothing was changed. | Leave model_prefix out, or create a new provider with the prefix you want. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| 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), 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": "provider_builtin", "message": "provider \"openai\" is built in and cannot be changed or removed; custom providers can"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
provider_builtin | Built-in provider | The provider ships with the agent and cannot be changed or removed over this API. Its key is managed with the /providers/{id}/auth routes. | Create a custom provider with POST /providers instead. |
provider_not_managed | Provider defined elsewhere | The custom provider is defined in a project’s providers file, which this API does not change. | Edit that project’s providers file inside the container. |
{ "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). A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "provider_invalid", "message": "base_url must use https", "details": { "field": "base_url" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
provider_invalid | Provider definition refused | The definition breaks a rule: an id outside the grammar, a model_prefix that differs from the id, an auth_scheme that does not match the wire_format, a base_url that is not public https, an unknown wire_format or auth_scheme, a refused header, or a model list that is empty, too long, or has duplicates. details.field names the member. Nothing was changed. | Fix the member details.field names and send the request again. |
{ "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, so the operation did not complete. The message names the store. A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log records a “store failure” entry with the store, the failure kind, and a path-free cause. |
{ "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. |
DELETE /api/v1/agent/providers/{id}
Section titled “DELETE /api/v1/agent/providers/{id}”Removes a custom provider and the API key stored for it. Built-in providers cannot be removed (409 provider_builtin), nor can a custom provider defined in a project’s providers file (409 provider_not_managed). An open session using one of its models becomes unconfigured: its next prompt fails with session_unconfigured naming the removed provider, and switching the session to another model (PATCH /sessions/{id}/model) recovers it. A session in a turn finishes that turn first. Agents and Jev settings that name one of its models keep the name and fail the same way until changed.
Process-wide: a per-request realm, cwd, config-dir, or container header returns 400 realm_scope_unsupported.
curl -X DELETE "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/acme" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.delete('acme');console.log(result);{ "id": "acme", "deleted": true, "api_key_removed": true}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
Statuses
Section titled “Statuses”{ "id": "acme", "deleted": true, "api_key_removed": true}| Error Code | Title | Description | Resolution |
|---|
{ "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. |
{ "code": "forbidden", "message": "request must arrive through the Hoody proxy"}| 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), 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": "provider_builtin", "message": "provider \"openai\" is built in and cannot be changed or removed; custom providers can"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
provider_builtin | Built-in provider | The provider ships with the agent and cannot be changed or removed over this API. Its key is managed with the /providers/{id}/auth routes. | Create a custom provider with POST /providers instead. |
provider_not_managed | Provider defined elsewhere | The custom provider is defined in a project’s providers file, which this API does not change. | Edit that project’s providers file inside the container. |
{ "code": "provider_invalid", "message": "base_url must use https", "details": { "field": "base_url" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
provider_invalid | Provider definition refused | The definition breaks a rule: an id outside the grammar, a model_prefix that differs from the id, an auth_scheme that does not match the wire_format, a base_url that is not public https, an unknown wire_format or auth_scheme, a refused header, or a model list that is empty, too long, or has duplicates. details.field names the member. Nothing was changed. | Fix the member details.field names and send the request again. |
{ "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, so the operation did not complete. The message names the store. A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log records a “store failure” entry with the store, the failure kind, and a path-free cause. |
{ "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. |
Provider authentication
Section titled “Provider authentication”GET /api/v1/agent/providers/{id}/auth
Section titled “GET /api/v1/agent/providers/{id}/auth”Reports a provider’s stored-credential state: whether an API key or OAuth login is stored, the effective default method, the passwordless (no-auth) flag, and the secret-free OAuth account pool.
curl -X GET "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/anthropic/auth" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const auth = await client.agent.providers.getAuth('anthropic');console.log(auth);{ "id": "anthropic", "default": "api_key", "passwordless": false, "api_key": { "set": true, "prefix": "sk-ant-abc1" }, "oauth": { "set": false, "primary": null, "accounts": [] }}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
Statuses
Section titled “Statuses”{ "id": "anthropic", "default": "api_key", "passwordless": false}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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. |
GET /api/v1/agent/providers/{id}/auth/accounts
Section titled “GET /api/v1/agent/providers/{id}/auth/accounts”Lists the secret-free OAuth account pool for a provider: key, label, active flag, cooldown. Never a token. 422 oauth_unsupported when the provider has no OAuth flow.
curl -X GET "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/openrouter/auth/accounts?page=1&limit=50" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
for await (const item of client.agent.providers.listAccountsIterator('openrouter', { page: 1, limit: 50 })) { console.log(item);}{ "items": [ { "key": "acct_01", "label": "Personal", "active": true, "cooldown": null } ], "meta": { "total": 1, "page": 1, "limit": 50 }}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
page | query | integer | No | 1-based page number for pagination. |
limit | query | integer | No | Maximum items per page (0 = no pagination). |
Statuses
Section titled “Statuses”{ "items": [], "meta": { "total": 0, "page": 1, "limit": 50 }}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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 |
|---|---|---|---|
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), 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": "oauth_unsupported", "message": "provider does not support OAuth login"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
oauth_unsupported | OAuth not supported | The provider has no OAuth login flow (no login id). | Use an API key for this provider, or pick a provider that supports OAuth. |
{ "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. The message names the store. A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log records a “store failure” entry with the store, the failure kind and a path-free cause. |
POST /api/v1/agent/providers/{id}/auth/accounts
Section titled “POST /api/v1/agent/providers/{id}/auth/accounts”Begins an OAuth login that ADDS to the provider’s account pool. Returns a {job_id}; drive it with the same poll and submit endpoints as POST /providers/{id}/auth/oauth. 422 oauth_unsupported when the provider has no OAuth flow.
curl -X POST "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/openrouter/auth/accounts" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.addAccount('openrouter', {});console.log(result);{ "job_id": "job-01HZX2EXAMPLE", "verification_uri": "https://openrouter.ai/auth?code=ABC123", "user_code": "ABC123"}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
Request Body
Section titled “Request Body”This endpoint accepts an empty JSON object. No fields are required; the login flow is started for the {id} provider.
{}Statuses
Section titled “Statuses”{ "job_id": "job-01HZX2EXAMPLE", "verification_uri": "https://openrouter.ai/auth?code=ABC123", "user_code": "ABC123"}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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 |
|---|---|---|---|
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, 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. |
job_not_found | Job not found | The referenced OAuth login job does not exist or has expired. | Restart the flow with POST /providers/{id}/auth/oauth and drive the new job id. |
{ "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). A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "oauth_unsupported", "message": "provider does not support OAuth login"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
oauth_unsupported | OAuth not supported | The provider has no OAuth login flow (no login id). | Use an API key for this provider, or pick a provider that supports OAuth. |
{ "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/providers/{id}/auth/oauth
Section titled “POST /api/v1/agent/providers/{id}/auth/oauth”Begins an interactive OAuth login for a provider and returns {success, job_id, verification_uri, user_code?}. Drive the flow with GET .../oauth/{job} (poll) and, for a manual or PKCE code, POST .../oauth/{job}/code (submit). The daemon’s own admin check may still refuse with 403 admin_unauthorized when the daemon was started with a separate administrator token, which an HTTP request cannot supply. The job carries only the non-secret verification URL and user code. 422 oauth_unsupported when the provider has no OAuth flow.
curl -X POST "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/openrouter/auth/oauth" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"add_account": true}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.startOauth('openrouter', { add_account: true });console.log(result);{ "success": true, "job_id": "job-01HZX2EXAMPLE", "verification_uri": "https://openrouter.ai/auth?code=ABC123", "user_code": "ABC123"}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
add_account | boolean | No | When true, the login ADDS to the provider’s OAuth account pool instead of replacing the primary login. |
Statuses
Section titled “Statuses”{ "success": true, "job_id": "job-01HZX2EXAMPLE", "verification_uri": "https://openrouter.ai/auth?code=ABC123", "user_code": "ABC123"}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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 |
|---|---|---|---|
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, 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. |
job_not_found | Job not found | The referenced OAuth login job does not exist or has expired. | Restart the flow with POST /providers/{id}/auth/oauth and drive the new job id. |
{ "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). A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "oauth_unsupported", "message": "provider does not support OAuth login"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
oauth_unsupported | OAuth not supported | The provider has no OAuth login flow (no login id). | Use an API key for this provider, or pick a provider that supports OAuth. |
{ "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/providers/{id}/auth/oauth/{job}
Section titled “GET /api/v1/agent/providers/{id}/auth/oauth/{job}”Polls an in-flight OAuth login job. Reports state:"pending" until the user authorizes, then state:"complete" (with the now-current secret-free auth status) or state:"error" (with a scrubbed error message).
curl -X GET "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/openrouter/auth/oauth/job-01HZX2EXAMPLE" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const snapshot = await client.agent.providers.pollOauth('openrouter', 'job-01HZX2EXAMPLE');console.log(snapshot);{ "state": "pending", "verification_uri": "https://openrouter.ai/auth?code=ABC123", "user_code": "ABC123", "needs_code": false}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
job | path | string | Yes | The job. |
Statuses
Section titled “Statuses”{ "state": "pending", "verification_uri": "https://openrouter.ai/auth?code=ABC123", "user_code": "ABC123", "needs_code": false}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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 |
|---|---|---|---|
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, 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": "job_not_found", "message": "no such OAuth job (it may have expired)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
job_not_found | Job not found | The referenced OAuth login job does not exist or has expired (idle past its TTL, or the daemon restarted). | Restart the flow with POST /providers/{id}/auth/oauth and drive the new job id. |
{ "code": "oauth_unsupported", "message": "provider does not support OAuth login"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
oauth_unsupported | OAuth not supported | The provider has no OAuth login flow (no login id). | Use an API key for this provider, or pick a provider that supports OAuth. |
{ "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/providers/{id}/auth/oauth/{job}/code
Section titled “POST /api/v1/agent/providers/{id}/auth/oauth/{job}/code”Supplies the authorization code (or full redirect URL) a blocked OAuth flow is waiting for. Idempotent-safe: a second submit on an already-fed job is a no-op (submitted:false). 404 job_not_found when the job is unknown or expired.
curl -X POST "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/openrouter/auth/oauth/job-01HZX2EXAMPLE/code" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"code": "AUTH_CODE_FROM_PROVIDER"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.submitOauthCode('openrouter', 'job-01HZX2EXAMPLE', { code: 'AUTH_CODE_FROM_PROVIDER' });console.log(result);{ "submitted": true, "message": "code accepted"}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
job | path | string | Yes | The job. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
code | string | Yes | The authorization code (or the full redirect URL) to complete the exchange. |
Statuses
Section titled “Statuses”{ "submitted": true, "message": "code accepted"}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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 |
|---|---|---|---|
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, 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": "job_not_found", "message": "no such OAuth job (it may have expired)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
not_found | Not found | The requested resource does not exist. | Verify the path and identifier. |
job_not_found | Job not found | The referenced OAuth login job does not exist or has expired. | Restart the flow with POST /providers/{id}/auth/oauth and drive the new job id. |
{ "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). A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "oauth_unsupported", "message": "provider does not support OAuth login"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
oauth_unsupported | OAuth not supported | The provider has no OAuth login flow (no login id). | Use an API key for this provider, or pick a provider that supports OAuth. |
{ "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. |
PUT /api/v1/agent/providers/{id}/auth/api-key
Section titled “PUT /api/v1/agent/providers/{id}/auth/api-key”Stores a provider’s API key in the agent’s owner-only credential file inside the container. The reply carries only the secret-free auth status (10-character prefix).
422 auth_method_unsupported for a passwordless provider.
curl -X PUT "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/anthropic/auth/api-key" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"api_key": "sk-ant-EXAMPLE-KEY"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.setApiKey('anthropic', { api_key: 'sk-ant-EXAMPLE-KEY' });console.log(result);{ "auth": { "id": "anthropic", "default": "api_key", "api_key": { "set": true, "prefix": "sk-ant-EXA1" } }}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
api_key | string | Yes | The provider API key. Stored in the agent’s owner-only credential file; the reply echoes only a prefix. |
Statuses
Section titled “Statuses”{ "auth": { "id": "anthropic", "default": "api_key", "api_key": { "set": true, "prefix": "sk-ant-EXA1" } }}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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 |
|---|---|---|---|
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, 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). A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "auth_method_unsupported", "message": "provider is passwordless (no API key method)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
oauth_unsupported | OAuth not supported | The provider has no OAuth login flow (no login id). | Use an API key for this provider, or pick a provider that supports OAuth. |
auth_method_unsupported | Auth method unsupported | The requested credential method is not available for this provider (for example an API-key default on a passwordless provider). | Pick a method the provider supports; a passwordless provider needs no default. |
credential_not_stored | Credential not stored | The default method cannot be promoted because no credential of that kind is stored for the provider yet. | Store the credential first, then set the default. |
{ "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. The message names the store. A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log records a “store failure” entry with the store, the failure kind and a path-free cause. |
{ "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. |
DELETE /api/v1/agent/providers/{id}/auth/api-key
Section titled “DELETE /api/v1/agent/providers/{id}/auth/api-key”Removes a provider’s stored API key and its default-method marker. Idempotent. Returns the resulting secret-free auth status.
curl -X DELETE "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/anthropic/auth/api-key" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.deleteApiKey('anthropic');console.log(result);{ "auth": { "id": "anthropic", "default": null, "api_key": { "set": false } }}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
Statuses
Section titled “Statuses”{ "auth": { "id": "anthropic", "default": null, "api_key": { "set": false } }}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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 |
|---|---|---|---|
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, 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": "auth_method_unsupported", "message": "provider is passwordless (no API key method)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
oauth_unsupported | OAuth not supported | The provider has no OAuth login flow (no login id). | Use an API key for this provider, or pick a provider that supports OAuth. |
auth_method_unsupported | Auth method unsupported | The requested credential method is not available for this provider. | Pick a method the provider supports; a passwordless provider needs no default. |
credential_not_stored | Credential not stored | The default method cannot be promoted because no credential of that kind is stored for the provider yet. | Store the credential first, then set the default. |
{ "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. The message names the store. A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log records a “store failure” entry with the store, the failure kind and a path-free cause. |
{ "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. |
PUT /api/v1/agent/providers/{id}/auth/default
Section titled “PUT /api/v1/agent/providers/{id}/auth/default”Sets the effective default credential method for a provider (api_key or oauth). The daemon validates that the method is available AND that a credential of that kind is actually stored (else 422). 422 oauth_unsupported, auth_method_unsupported, or credential_not_stored as appropriate.
curl -X PUT "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/openrouter/auth/default" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"default": "oauth"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.setDefaultAuth('openrouter', { default: 'oauth' });console.log(result);{ "auth": { "id": "openrouter", "default": "oauth", "oauth": { "set": true } }}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
default | string | Yes | The default method: api_key or oauth. Must be a method the provider supports AND has a stored credential for. |
Statuses
Section titled “Statuses”{ "auth": { "id": "openrouter", "default": "oauth", "oauth": { "set": true } }}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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 |
|---|---|---|---|
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, 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). A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "credential_not_stored", "message": "no API key is stored for this provider"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
oauth_unsupported | OAuth not supported | The provider has no OAuth login flow (no login id). | Use an API key for this provider, or pick a provider that supports OAuth. |
auth_method_unsupported | Auth method unsupported | The requested credential method is not available for this provider. | Pick a method the provider supports; a passwordless provider needs no default. |
credential_not_stored | Credential not stored | The default method cannot be promoted because no credential of that kind is stored for the provider yet. | Store the credential first, then set the default. |
{ "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. The message names the store. A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log records a “store failure” entry with the store, the failure kind and a path-free cause. |
{ "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. |
PUT /api/v1/agent/providers/{id}/auth/accounts/{key}/active
Section titled “PUT /api/v1/agent/providers/{id}/auth/accounts/{key}/active”Makes one pooled OAuth account active for a provider. The {key} path value names the account. Returns the resulting secret-free account pool. 422 oauth_unsupported when the provider has no OAuth flow.
curl -X PUT "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/openrouter/auth/accounts/acct_01/active" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.useAccount('openrouter', 'acct_01', {});console.log(result);{ "accounts": [ { "key": "acct_01", "label": "Personal", "active": true, "cooldown": null } ]}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
key | path | string | Yes | The key. |
Request Body
Section titled “Request Body”This endpoint accepts an empty JSON object. No body fields are required; the account is identified by the {key} path value.
{}Statuses
Section titled “Statuses”{ "accounts": [ { "key": "acct_01", "label": "Personal", "active": true, "cooldown": null } ]}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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 |
|---|---|---|---|
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, 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). A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "oauth_unsupported", "message": "provider does not support OAuth login"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
oauth_unsupported | OAuth not supported | The provider has no OAuth login flow (no login id). | Use an API key for this provider, or pick a provider that supports OAuth. |
{ "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. The message names the store. A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log records a “store failure” entry with the store, the failure kind and a path-free cause. |
{ "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. |
DELETE /api/v1/agent/providers/{id}/auth/accounts/{key}
Section titled “DELETE /api/v1/agent/providers/{id}/auth/accounts/{key}”Drops one pooled OAuth account for a provider. The {key} path value names the account. Returns the resulting secret-free account pool. 422 oauth_unsupported when the provider has no OAuth flow.
curl -X DELETE "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/openrouter/auth/accounts/acct_01" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.removeAccount('openrouter', 'acct_01');console.log(result);{ "accounts": []}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
key | path | string | Yes | The key. |
Statuses
Section titled “Statuses”{ "accounts": []}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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 |
|---|---|---|---|
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, 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": "oauth_unsupported", "message": "provider does not support OAuth login"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
oauth_unsupported | OAuth not supported | The provider has no OAuth login flow (no login id). | Use an API key for this provider, or pick a provider that supports OAuth. |
{ "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. The message names the store. A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log records a “store failure” entry with the store, the failure kind and a path-free cause. |
{ "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. |
DELETE /api/v1/agent/providers/{id}/auth/oauth
Section titled “DELETE /api/v1/agent/providers/{id}/auth/oauth”Removes a provider’s stored OAuth credentials. Idempotent. Returns the resulting secret-free auth status. 422 oauth_unsupported when the provider has no OAuth flow.
curl -X DELETE "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/providers/openrouter/auth/oauth" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.providers.logoutOauth('openrouter');console.log(result);{ "auth": { "id": "openrouter", "default": null, "oauth": { "set": false } }}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The provider id. |
Statuses
Section titled “Statuses”{ "auth": { "id": "openrouter", "default": null, "oauth": { "set": false } }}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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 |
|---|---|---|---|
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, 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. |
job_not_found | Job not found | The referenced OAuth login job does not exist or has expired. | Restart the flow with POST /providers/{id}/auth/oauth and drive the new job id. |
{ "code": "oauth_unsupported", "message": "provider does not support OAuth login"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
oauth_unsupported | OAuth not supported | The provider has no OAuth login flow (no login id). | Use an API key for this provider, or pick a provider that supports OAuth. |
{ "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. The message names the store. A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log records a “store failure” entry with the store, the failure kind and a path-free cause. |
{ "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. |
Jev: decision subsystem
Section titled “Jev: decision subsystem”Jev evaluates state against named questions and returns calibrated answers. Question types are noul (yes/no, returning {noul: P(yes)}), choice (1 to 255 labels, returning {choice, confidence, probabilities}), and score (1 to 10 levels low to high, returning {score, confidence, probabilities, legend}).
GET /api/v1/agent/jev/models
Section titled “GET /api/v1/agent/jev/models”Lists every Jev model spec per provider, plus each provider’s quirks. models is [{spec, display_name, provider, default, key_set, active}] and providers is [{id, display_name, key_set, key_env_var, default_model, quirks}]. quirks records each provider’s differences: nullable_instructions, partial_noul_criteria, routing_extras (OpenRouter’s provider, session_id, trace, user, x_session_id), cost_in_body, request_id_header, accepted_models, rejected_models, and verified. effective_model names the model in use.
curl -X GET "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/jev/models" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const models = await client.agent.jev.listModels();console.log(models);{ "models": [ { "spec": "typesafe/jev-latest", "display_name": "Jev Latest", "provider": "typesafe", "default": true, "key_set": true, "active": true } ], "providers": [ { "id": "typesafe", "display_name": "TypeSafe", "key_set": true, "key_env_var": "TYPESAFE_API_KEY", "default_model": "jev-latest", "quirks": { "nullable_instructions": true, "verified": true } } ], "effective_model": "typesafe/jev-latest"}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Hoody-Cwd | header | string | No | Per-request working-directory scope: the .hoody project layer / record cwd / tool+workflow cwd. |
X-Hoody-Config-Dir | header | string | No | Per-request --config-dir override selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
Statuses
Section titled “Statuses”{ "models": [], "providers": [], "effective_model": ""}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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": "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. |
GET /api/v1/agent/jev/settings
Section titled “GET /api/v1/agent/jev/settings”Returns the Jev settings of this install and the usage since the agent started. model is the explicit jev.model ("" when unset); effective_model is the model calls use, with source "explicit" or "derived" (derived = the default of the first provider with a key, preferring typesafe, then openrouter, then vercel). provider and key_set describe the effective model’s provider. usage totals every Jev call this agent made: calls, errors, attempts, cost_usd (null once any call’s spend is unknown), known_cost_usd (the proven subtotal), unknown_cost_calls, tokens, and the last call’s latency, model, and error code.
X-Hoody-Config-Dir selects the install; a realm or container header is rejected.
curl -X GET "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/jev/settings" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const settings = await client.agent.jev.getSettings();console.log(settings);{ "enabled": true, "model": "", "effective_model": "typesafe/jev-latest", "source": "derived", "provider": "typesafe", "key_set": true, "timeout_ms": 15000, "usage": { "calls": 42, "errors": 0, "attempts": 42, "cost_usd": 0.0123, "known_cost_usd": 0.0123, "unknown_cost_calls": 0, "tokens": 18000, "last_latency_ms": 320, "last_model": "typesafe/jev-latest", "last_error_code": null }}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 selecting which on-disk .hoody install a stateless read/write resolves against. |
X-Hoody-Container | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. |
Statuses
Section titled “Statuses”{ "enabled": true, "model": "", "effective_model": "typesafe/jev-latest", "source": "derived"}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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": "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. |
PUT /api/v1/agent/jev/settings
Section titled “PUT /api/v1/agent/jev/settings”Patches jev.enabled, jev.model, and/or jev.timeout_ms. An omitted field is left unchanged, and at least one is required. model is a provider/model spec from GET /jev/models, or any model id the provider accepts (a new Jev version needs no agent update). Model ids a provider is known to reject are refused (OpenRouter rejects jev-1.13.0, jev-preview, and jev-1.12). "" clears the model so it is derived again. Returns the updated settings, as GET /jev/settings does.
Keys are not set here; use PUT /providers/{id}/auth/api-key.
curl -X PUT "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/jev/settings" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"enabled": true, "model": "typesafe/jev-latest", "timeout_ms": 15000}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const settings = await client.agent.jev.updateSettings({ enabled: true, model: 'typesafe/jev-latest', timeout_ms: 15000 });console.log(settings);{ "enabled": true, "model": "typesafe/jev-latest", "effective_model": "typesafe/jev-latest", "source": "explicit", "provider": "typesafe", "key_set": true, "timeout_ms": 15000}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. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
enabled | boolean | No | Whether Jev calls are allowed (default true). When false every decide/test call fails with jev_disabled. |
model | string | No | Provider/model spec, for example typesafe/jev-latest or openrouter/typesafe/jev-1.13. "" returns to the derived default. |
timeout_ms | integer | No | Budget in milliseconds for one whole call, the one retry included: 100 to 120000 (default 15000). |
Statuses
Section titled “Statuses”{ "enabled": true, "model": "typesafe/jev-latest", "effective_model": "typesafe/jev-latest", "source": "explicit"}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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. |
jev_bad_request | Invalid Jev request | The request broke a Jev rule before any network call (details.field names the slot), or the provider rejected it. | Fix the named field. GET /jev/models lists each provider’s accepted model ids and nullability rules. |
{ "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": "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). A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "rate_limited", "message": "request rate limit exceeded"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
rate_limited | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the Retry-After header and retry; reduce the request rate. |
{ "code": "internal_error", "message": "internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
internal_error | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. |
store_failed | Agent store failed | One of the agent’s own stores could not be read, parsed, locked or written. The message names the store. A damaged store is left as it was rather than rewritten. | Retry once; if it persists, repair the named store inside the container. The agent’s log records a “store failure” entry with the store, the failure kind and a path-free cause. |
POST /api/v1/agent/jev/decide
Section titled “POST /api/v1/agent/jev/decide”Evaluates state against named questions and returns calibrated answers. The body is the provider request, passed through without narrowing. state is a string, a JSON object, or a JSON array (never null, a number, or a boolean). questions maps a name to {type:"noul", instructions, criteria?}, {type:"choice", instructions, criteria}, or {type:"score", instructions, criteria}. Optional: model, timeout_ms (100 to 120000), and OpenRouter’s provider, session_id, trace, user, x_session_id.
Checks run before any network call (label and level limits, per-provider nullability). A transport failure, 5xx, or 429 is retried once (a 429 only when its Retry-After is at most 5 s and fits the budget). Every X-Hoody-Realm / ?realm= (including global), X-Hoody-Cwd, X-Hoody-Container, and X-Hoody-Config-Dir is refused with 400 realm_scope_unsupported.
curl -X POST "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/jev/decide" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "state": { "ticket": "I was charged twice for my order." }, "questions": { "billing": { "type": "noul", "instructions": "Is this about billing?" }, "urgency": { "type": "score", "instructions": "How urgent is it?", "criteria": ["low", "medium", "high"] } } }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const decision = await client.agent.jev.decide({ state: { ticket: 'I was charged twice for my order.' }, questions: { billing: { type: 'noul', instructions: 'Is this about billing?' }, urgency: { type: 'score', instructions: 'How urgent is it?', criteria: ['low', 'medium', 'high'] } }});console.log(decision);{ "answers": { "billing": { "noul": 0.92 }, "urgency": { "score": "high", "confidence": 0.78, "probabilities": { "low": 0.05, "medium": 0.17, "high": 0.78 }, "legend": ["low", "medium", "high"] } }, "model": "typesafe/jev-latest", "requested_model": "typesafe/jev-latest", "provider": "typesafe", "upstream_provider": null, "id": "dec-01HZX2EXAMPLE", "request_id": "req-01HZX2EXAMPLE", "generation_id": null, "ignored_fields": [], "usage": { "cost_usd": 0.00021, "known_cost_usd": 0.00021, "cost_unknown_attempts": 0, "attempts": 1, "latency_ms": 320, "model": "typesafe/jev-latest", "request_id": "req-01HZX2EXAMPLE", "input_tokens": 280, "output_tokens": 18 }}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. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
state | string | object | array | Yes | What the questions are about: a string, a JSON object, or a JSON array (never null, a number, or a boolean). Passed to the provider as is. |
questions | object | Yes | Question name to {type: noul|choice|score, instructions, criteria}. At least one. |
model | string | No | Provider/model spec to use instead of the effective model. |
timeout_ms | integer | No | Budget for this call in milliseconds (100 to 120000), the one retry included. |
provider | object | No | OpenRouter only: provider routing preferences. |
session_id | string | No | OpenRouter only: session grouping id (up to 256 characters). |
trace | object | No | OpenRouter only: trace configuration. |
user | string | No | OpenRouter only: end-user id (up to 256 characters). |
x_session_id | string | No | OpenRouter only: sent as the x-session-id header. |
Statuses
Section titled “Statuses”{ "answers": {}, "model": "typesafe/jev-latest"}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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. |
jev_bad_request | Invalid Jev request | The request broke a Jev rule before any network call (details.field names the slot), or the provider rejected it. | Fix the named field. GET /jev/models lists each provider’s accepted model ids and nullability rules. |
{ "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": "jev_disabled", "message": "jev_disabled: Jev is disabled (jev.enabled is false)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_disabled | Jev is disabled | jev.enabled is false for this install. | PUT /jev/settings with {"enabled": true}. |
{ "code": "jev_too_large", "message": "jev_too_large: state plus the longest question is about 40210 tokens; the limit is 32000"}| 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. |
jev_too_large | Jev request too large | State plus the longest single question exceeds the 32k-token context: the provider’s max_tokens_exceeded or a 413. | Shorten the state or split the question. |
{ "code": "jev_no_key", "message": "jev_no_key: no credential for typesafe — set TYPESAFE_API_KEY or add a key in the Models tab"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_no_key | No key for the Jev provider | The provider of the effective Jev model has no API key usable for direct API calls. | Store the provider’s key with PUT /providers/{id}/auth/api-key, or choose a model whose provider has one. |
{ "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": "jev_invalid_answer", "message": "jev_invalid_answer: answer \"intent\": probabilities sum to 0.9100, not 1"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_auth | Provider rejected the key | The Jev provider answered 401 or 403 for the stored key. | Replace the provider’s key. |
jev_payment | Provider account out of credit | The Jev provider answered 402. | Add credit to the provider account. |
jev_invalid_answer | Invalid Jev answer | The provider answered, but an answer was missing, had the wrong type, or was inconsistent. Its usage is still counted. | Retry once; report persistent failures with details.request_id. |
{ "code": "jev_unavailable", "message": "jev_unavailable: the provider failed (HTTP 529)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_unavailable | Jev provider unavailable | A transport failure, 5xx, or rate limit (429) persisted through the one retry, or left no room for it. | Retry later. |
jev_cancelled | Jev call cancelled | The client went away before the decision completed. | Retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve, so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
{ "code": "jev_timeout", "message": "jev_timeout: the Jev call ran out of time"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_timeout | Jev call timed out | The call’s budget (jev.timeout_ms or the request’s timeout_ms) ran out, retries included. | Raise timeout_ms or retry. |
POST /api/v1/agent/jev/test
Section titled “POST /api/v1/agent/jev/test”Sends one small yes/no question (about 280 input tokens, a fraction of a cent) to the effective model, or to model when given, and returns {ok, latency_ms, model, provider, answer, usage}. A failure returns the typed jev_* error, so this is how to check a key and model before relying on them. The call counts toward the usage in GET /jev/settings.
curl -X POST "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/jev/test" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"model": "typesafe/jev-latest"}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const result = await client.agent.jev.test({ model: 'typesafe/jev-latest' });console.log(result);{ "ok": true, "latency_ms": 280, "model": "typesafe/jev-latest", "provider": "typesafe", "answer": { "noul": 0.5 }, "usage": { "input_tokens": 280, "output_tokens": 1, "cost_usd": 0.00012 }}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. |
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
model | string | No | A provider/model spec to test instead of the effective model. |
Statuses
Section titled “Statuses”{ "ok": true, "latency_ms": 280, "model": "typesafe/jev-latest", "provider": "typesafe", "answer": { "noul": 0.5 }, "usage": { "input_tokens": 280, "output_tokens": 1, "cost_usd": 0.00012 }}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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. |
jev_bad_request | Invalid Jev request | The request broke a Jev rule before any network call, or the provider rejected it. | Fix the named field. GET /jev/models lists each provider’s accepted model ids and nullability rules. |
{ "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": "jev_disabled", "message": "jev_disabled: Jev is disabled (jev.enabled is false)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_disabled | Jev is disabled | jev.enabled is false for this install. | PUT /jev/settings with {"enabled": true}. |
{ "code": "jev_too_large", "message": "jev_too_large: state plus the longest question is about 40210 tokens; the limit is 32000"}| 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. |
jev_too_large | Jev request too large | State plus the longest single question exceeds the 32k-token context. | Shorten the state or split the question. |
{ "code": "jev_no_key", "message": "jev_no_key: no credential for typesafe — set TYPESAFE_API_KEY or add a key in the Models tab"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_no_key | No key for the Jev provider | The provider of the effective Jev model has no API key usable for direct API calls. | Store the provider’s key with PUT /providers/{id}/auth/api-key, or choose a model whose provider has one. |
{ "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": "jev_invalid_answer", "message": "jev_invalid_answer: answer \"intent\": probabilities sum to 0.9100, not 1"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_auth | Provider rejected the key | The Jev provider answered 401 or 403 for the stored key. | Replace the provider’s key. |
jev_payment | Provider account out of credit | The Jev provider answered 402. | Add credit to the provider account. |
jev_invalid_answer | Invalid Jev answer | The provider answered, but an answer was missing, had the wrong type, or was inconsistent. Its usage is still counted. | Retry once; report persistent failures with details.request_id. |
{ "code": "jev_unavailable", "message": "jev_unavailable: the provider failed (HTTP 529)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_unavailable | Jev provider unavailable | A transport failure, 5xx, or rate limit (429) persisted through the one retry, or left no room for it. | Retry later. |
jev_cancelled | Jev call cancelled | The client went away before the decision completed. | Retry. |
{ "code": "jev_timeout", "message": "jev_timeout: the Jev call ran out of time"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_timeout | Jev call timed out | The call’s budget (jev.timeout_ms or the request’s timeout_ms) ran out, retries included. | Raise timeout_ms or retry. |
POST /api/v1/agent/sessions/{id}/jev/decide
Section titled “POST /api/v1/agent/sessions/{id}/jev/decide”The same decision as POST /jev/decide (same body, reply, and jev_* errors), made for a LIVE session: the Jev settings and keys come from the session’s own config root, and the call’s usage is emitted as event.jev_usage on the session’s stream and added to the session’s cost, as well as to the totals in GET /jev/settings. The body’s session_id stays OpenRouter’s grouping id; the session is the path’s {id}. A session that is not live or not visible to this caller is a plain 404. X-Hoody-Realm or ?realm= names the realm the session is looked up in (global or a 24-hex realm id; omitted, the agent’s current realm); a session in another realm, or in a realm this login does not serve, is the plain 404. The session’s scope is frozen: an X-Hoody-Container, X-Hoody-Cwd or X-Hoody-Config-Dir header returns 400 realm_scope_unsupported.
curl -X POST "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/sessions/sess-01HZX2EXAMPLE/jev/decide" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "state": { "ticket": "I was charged twice for my order." }, "questions": { "billing": { "type": "noul", "instructions": "Is this about billing?" } } }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const decision = await client.agent.jev.decideForSession('sess-01HZX2EXAMPLE', { state: { ticket: 'I was charged twice for my order.' }, questions: { billing: { type: 'noul', instructions: 'Is this about billing?' } }});console.log(decision);{ "answers": { "billing": { "noul": 0.92 } }, "model": "typesafe/jev-latest", "requested_model": "typesafe/jev-latest", "provider": "typesafe", "upstream_provider": null, "id": "dec-01HZX2EXAMPLE", "request_id": "req-01HZX2EXAMPLE", "generation_id": null, "ignored_fields": [], "usage": { "cost_usd": 0.00021, "known_cost_usd": 0.00021, "cost_unknown_attempts": 0, "attempts": 1, "latency_ms": 320, "model": "typesafe/jev-latest", "request_id": "req-01HZX2EXAMPLE", "input_tokens": 280, "output_tokens": 1 }}Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | The session id. |
X-Hoody-Realm | header | string | No | Per-request realm selector: “global” (not tied to a realm) or a 24-hex realm id (also accepted as ?realm=). On a session route it names the realm the session is looked up in: 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”| Name | Type | Required | Description |
|---|---|---|---|
state | string | object | array | Yes | What the questions are about: a string, a JSON object, or a JSON array. |
questions | object | Yes | Question name to {type: noul|choice|score, instructions, criteria}. At least one. |
model | string | No | Provider/model spec to use instead of the effective model. |
timeout_ms | integer | No | Budget for this call in milliseconds (100 to 120000). |
provider | object | No | OpenRouter only: provider routing preferences. |
session_id | string | No | OpenRouter only: session grouping id (up to 256 characters). |
trace | object | No | OpenRouter only: trace configuration. |
user | string | No | OpenRouter only: end-user id (up to 256 characters). |
x_session_id | string | No | OpenRouter only: sent as the x-session-id header. |
Statuses
Section titled “Statuses”{ "answers": {}, "model": "typesafe/jev-latest"}| Error Code | Title | Description | Resolution |
|---|
{ "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 or 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. |
jev_bad_request | Invalid Jev request | The request broke a Jev rule before any network call, or the provider rejected it. | Fix the named field. GET /jev/models lists each provider’s accepted model ids and nullability rules. |
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": "jev_disabled", "message": "jev_disabled: Jev is disabled (jev.enabled is false)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_disabled | Jev is disabled | jev.enabled is false for this install. | PUT /jev/settings with {"enabled": true}. |
{ "code": "jev_too_large", "message": "jev_too_large: state plus the longest question is about 40210 tokens; the limit is 32000"}| 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. |
jev_too_large | Jev request too large | State plus the longest single question exceeds the 32k-token context. | Shorten the state or split the question. |
{ "code": "jev_no_key", "message": "jev_no_key: no credential for typesafe — set TYPESAFE_API_KEY or add a key in the Models tab"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_no_key | No key for the Jev provider | The provider of the effective Jev model has no API key usable for direct API calls. | Store the provider’s key with PUT /providers/{id}/auth/api-key, or choose a model whose provider has one. |
{ "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": "jev_invalid_answer", "message": "jev_invalid_answer: answer \"intent\": probabilities sum to 0.9100, not 1"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_auth | Provider rejected the key | The Jev provider answered 401 or 403 for the stored key. | Replace the provider’s key. |
jev_payment | Provider account out of credit | The Jev provider answered 402. | Add credit to the provider account. |
jev_invalid_answer | Invalid Jev answer | The provider answered, but an answer was missing, had the wrong type, or was inconsistent. Its usage is still counted. | Retry once; report persistent failures with details.request_id. |
{ "code": "jev_unavailable", "message": "jev_unavailable: the provider failed (HTTP 529)"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_unavailable | Jev provider unavailable | A transport failure, 5xx, or rate limit (429) persisted through the one retry, or left no room for it. | Retry later. |
jev_cancelled | Jev call cancelled | The client went away before the decision completed. | Retry. |
restriction_unknown | Login restriction unknown | The agent cannot read yet which realms its login may serve, so it refuses realm-scoped requests until it can. Nothing was read or changed. | Retry after the restriction is readable. |
{ "code": "jev_timeout", "message": "jev_timeout: the Jev call ran out of time"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
jev_timeout | Jev call timed out | The call’s budget (jev.timeout_ms or the request’s timeout_ms) ran out, retries included. | Raise timeout_ms or retry. |
Completions
Section titled “Completions”POST /api/v1/agent/completions
Section titled “POST /api/v1/agent/completions”Sends messages (and an optional system prompt) to model once, with no tools, no session, and no transcript, and answers the whole reply as JSON. settings sets per-call thinking, temperature, max_tokens, and response_format; a setting the model or its adapter cannot honour is refused with 400 unsupported_setting before any network call, never dropped. applied reports what was actually sent. A reply that stops on max_tokens, refusal, or content_filter is still a 200 carrying the text so far (read stop_reason); a tool call or an error stop is 502 upstream_error. json_valid (present when response_format is set) is a syntax check of the text, not a schema check. OpenAI-style json_object mode needs the word “json” in the prompt.
Usage is recorded in the usage log whenever the model was called, success or not. This route takes no stream slot: each client IP may hold up to the gateway’s completion cap (default 32) in flight, above which it answers 503 service_unavailable with Retry-After; the request-rate limit applies as usual. Fan-out clients should use this route rather than client.agent.completions.create() with stream: true.
Every X-Hoody-Realm / ?realm= (including global), X-Hoody-Cwd, X-Hoody-Container, and X-Hoody-Config-Dir is refused with 400 realm_scope_unsupported: a completion has no scope.
curl -X POST "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/completions" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "model": "xiaomi-token-plan-sgp/mimo-v2.5", "system": "Reply with a JSON object.", "messages": [{ "role": "user", "content": "List three primary colours as json." }], "settings": { "thinking": { "type": "disabled" }, "temperature": 0.2, "max_tokens": 2048, "response_format": { "type": "json_object" } }, "timeout_ms": 120000 }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const completion = await client.agent.completions.create({ model: 'xiaomi-token-plan-sgp/mimo-v2.5', system: 'Reply with a JSON object.', messages: [{ role: 'user', content: 'List three primary colours as json.' }], settings: { thinking: { type: 'disabled' }, temperature: 0.2, max_tokens: 2048, response_format: { type: 'json_object' } }, timeout_ms: 120000});console.log(completion);{ "status": "ok", "model": "xiaomi-token-plan-sgp/mimo-v2.5", "text": "{ \"colours\": [\"red\", \"green\", \"blue\"] }", "stop_reason": "end_turn", "json_valid": true, "usage": { "input_tokens": 32, "output_tokens": 18, "cache_read_tokens": 0, "cache_write_tokens": 0, "reasoning_tokens": 0 }, "cost_usd": 0.00012, "cost_source": "provider", "applied": { "thinking": "disabled", "max_tokens": 2048, "response_format": "json_object" }, "duration_ms": 480}This endpoint takes no parameters.
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
model | string | Yes | A provider/model spec, for example xiaomi-token-plan-sgp/mimo-v2.5. The provider prefix must be catalogued. An unknown provider or an empty model name is 422 model_unavailable (reason unknown_model); a fusion/ composite is 422 model_unavailable (reason fusion). |
system | string | No | Optional system prompt. |
messages | array | Yes | 1 to 1000 turns, oldest first; the last must be a user turn. |
settings | object | No | Optional per-call model settings. A setting the model cannot honour is 400 unsupported_setting, never dropped. |
timeout_ms | integer | No | Deadline for the whole call, 1 to 600000 ms (default 120000). Reaching it is 504 timeout. |
max_tokens | integer | No | Alias of settings.max_tokens (output-token cap, 1 to 1000000). Sending both with different values is 400 bad_request (details.field max_tokens). |
Each message in messages is an object with:
| Name | Type | Required | Description |
|---|---|---|---|
role | string | Yes | Who spoke. One of: user, assistant. |
content | string | Yes | Non-empty text. |
The settings object supports:
| Name | Type | Required | Description |
|---|---|---|---|
thinking | object | No | Reasoning: {type: enabled|disabled, effort?} — {low, medium, high, xhigh, max}. effort is allowed only with enabled. Absent keeps the model’s default reasoning. |
temperature | number | No | Sampling temperature, 0 to 2 (the model’s own range applies; many models accept it only with thinking disabled). |
max_tokens | integer | No | Output cap, 1 to 1000000, clamped to the model’s output limit. |
response_format | object | No | {type: json_object}, or {type: json_schema, schema: {...}, name?, strict?}. name matches ^[A-Za-z0-9_-]{1,64}$ and defaults to "response". |
Statuses
Section titled “Statuses”{ "status": "ok", "model": "xiaomi-token-plan-sgp/mimo-v2.5", "text": "ok", "stop_reason": "end_turn", "usage": { "input_tokens": 1, "output_tokens": 1, "cache_read_tokens": 0, "cache_write_tokens": 0 }, "cost_usd": 0.0, "cost_source": "provider", "applied": { "thinking": "disabled", "max_tokens": 2048 }, "duration_ms": 10}| Error Code | Title | Description | Resolution |
|---|
{ "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. |
unsupported_setting | Unsupported model setting | A settings member cannot be honoured by this model or its adapter, so the call was refused before it was sent. details names it: {setting, value, provider, model, wire, reason, kind}. kind is provider (the model itself cannot do it) or adapter (this agent does not implement it for the model). | Drop or change the named setting (details.reason says why), or pick a model that supports it. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only or 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": "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). A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "model_unavailable", "message": "no credential for xiaomi-token-plan-sgp", "details": { "reason": "no_credentials" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
model_unavailable | Model unavailable | The model cannot serve a completion. details.reason: unknown_model (no such provider or model), no_credentials (the provider has no key or login), non_chat_provider (the provider serves no chat models), or fusion (a fusion/ composite cannot back one call). | Pick a model from GET /models, or add the provider’s credential. |
{ "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": "upstream_error", "message": "Rate limited by API", "details": { "reason": "http", "retryable": true, "upstream_status": 429 }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
upstream_error | Model provider error | The provider failed the call or returned a reply this route cannot use. details: {reason: http|protocol|incomplete_stream|unexpected_tool_call|error_stop, upstream_status? (the provider's HTTP status), retryable, retry_after_ms?}. A tool call is never executed. | Retry when details.retryable is true (after retry_after_ms when present); otherwise change the request or the model. |
{ "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. |
{ "code": "timeout", "message": "the completion ran out of time"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
timeout | Completion timed out | The call’s deadline (timeout_ms, default 120000) ran out before the reply finished. | Raise timeout_ms, lower max_tokens, or retry. |
POST /api/v1/agent/completions/stream
Section titled “POST /api/v1/agent/completions/stream”The same call as client.agent.completions.create() without stream, streamed as Server-Sent Events: start, then delta (text) and thinking frames as they arrive, then exactly one result (the body that call returns) or error, then end. Nothing is written before the model call starts: a failure before start (validation, unsupported_setting, model_unavailable, timeout, the stream cap) is an ordinary JSON error with its HTTP status. After start a failure is an error frame {code, message, details?} carrying the same codes (upstream_error, timeout, internal_error). A : ping comment is sent every 25 s while the stream is open. There is no retry: hint and the stream must not be replayed automatically: every call is billed. Read it with fetch and an AbortController; aborting cancels the model call.
Each open stream holds one of the client IP’s stream slots (shared with session streams, default 16); over the cap is 503 service_unavailable with Retry-After. Fan-out clients should use client.agent.completions.create() without stream.
Every X-Hoody-Realm / ?realm= (including global), X-Hoody-Cwd, X-Hoody-Container, and X-Hoody-Config-Dir is refused with 400 realm_scope_unsupported.
curl -N -X POST "https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com/api/v1/agent/completions/stream" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ -d '{ "model": "xiaomi-token-plan-sgp/mimo-v2.5", "messages": [{ "role": "user", "content": "List three primary colours as json." }] }'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.node-example-1.containers.hoody.com', token: process.env.HOODY_TOKEN });
const stream = await client.agent.completions.create({ model: 'xiaomi-token-plan-sgp/mimo-v2.5', messages: [{ role: 'user', content: 'List three primary colours as json.' }]}, { stream: true });for await (const event of stream) { console.log(event);}event: startdata: {"model":"xiaomi-token-plan-sgp/mimo-v2.5"}
event: deltadata: {"text":"{ \"colours\":"}
event: deltadata: {"text":" [\"red\", \"green\", \"blue\"] }"}
event: resultdata: {"status":"ok","model":"xiaomi-token-plan-sgp/mimo-v2.5","text":"{ \"colours\": [\"red\", \"green\", \"blue\"] }","stop_reason":"end_turn","usage":{"input_tokens":32,"output_tokens":18,"cache_read_tokens":0,"cache_write_tokens":0},"cost_usd":0.00012,"cost_source":"provider","applied":{"thinking":"provider_default","max_tokens":null},"duration_ms":480}
event: enddata: {}This endpoint takes no parameters.
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
model | string | Yes | A provider/model spec, for example xiaomi-token-plan-sgp/mimo-v2.5. The provider prefix must be catalogued. An unknown provider or an empty model name is 422 model_unavailable (reason unknown_model); a fusion/ composite is 422 model_unavailable (reason fusion). |
system | string | No | Optional system prompt. |
messages | array | Yes | 1 to 1000 turns, oldest first; the last must be a user turn. |
settings | object | No | Optional per-call model settings. A setting the model cannot honour is 400 unsupported_setting, never dropped. |
timeout_ms | integer | No | Deadline for the whole call, 1 to 600000 ms (default 120000). Reaching it is 504 timeout (an error frame once streaming). |
max_tokens | integer | No | Alias of settings.max_tokens (output-token cap, 1 to 1000000). Sending both with different values is 400 bad_request (details.field max_tokens). |
Statuses
Section titled “Statuses”event: startdata: {"model":"xiaomi-token-plan-sgp/mimo-v2.5"}
event: deltadata: {"text":"..."}
event: resultdata: {"status":"ok","model":"...","text":"...","stop_reason":"end_turn","usage":{...},"cost_usd":0.0,"cost_source":"provider","applied":{...},"duration_ms":10}
event: enddata: {}The body is a Server-Sent Events stream, not JSON.
| Error Code | Title | Description | Resolution |
|---|
{ "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. |
unsupported_setting | Unsupported model setting | A settings member cannot be honoured by this model or its adapter, so the call was refused before it was sent. details names it: {setting, value, provider, model, wire, reason, kind}. kind is provider (the model itself cannot do it) or adapter (this agent does not implement it for the model). | Drop or change the named setting (details.reason says why), or pick a model that supports it. |
realm_scope_unsupported | Realm scope unsupported | A per-request realm header was supplied to an active-only or 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": "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). A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. |
{ "code": "model_unavailable", "message": "no credential for xiaomi-token-plan-sgp", "details": { "reason": "no_credentials" }}| Error Code | Title | Description | Resolution |
|---|---|---|---|
model_unavailable | Model unavailable | The model cannot serve a completion. details.reason: unknown_model (no such provider or model), no_credentials (the provider has no key or login), non_chat_provider (the provider serves no chat models), or fusion (a fusion/ composite cannot back one call). | Pick a model from GET /models, or add the provider’s credential. |
{ "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. |
{ "code": "timeout", "message": "the completion ran out of time"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
timeout | Completion timed out | The call’s deadline (timeout_ms, default 120000) ran out before the reply finished. | Raise timeout_ms, lower max_tokens, or retry. |