Skip to content

The /api/agent/models/ namespace exposes the agent’s catalogue of bindable models and LLM providers, plus the credential management surface for those providers. Use these endpoints to discover what models are available, inspect provider capabilities, store API keys, drive interactive OAuth logins, and manage a pooled OAuth account roster per provider.

All endpoints in this namespace are stateless reads/writes against the on-disk .hoody install and the merged providers catalogue. They have no session dimension; a per-request X-Hoody-Cwd or X-Hoody-Config-Dir header is honored where the route resolves a cwd.


Lists the merged model catalogue: every catalogued provider model (spec, display name, context window, output limit, reasoning flag, display prices) AND every selectable fusion composite (spec = fusion/<slug>, with its method and member specs). 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.

This endpoint takes no request body.

NameInTypeRequiredDescription
pagequeryintegerNo1-based page number for pagination.
limitqueryintegerNoMaximum items per page (0 = no pagination).
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override selecting which on-disk .hoody install a stateless read/write resolves.
X-Hoody-ContainerheaderstringNoPer-request bound remote container (omitted = local).
X-Hoody-RealmheaderstringNoPer-request realm selector: global or a 24-hex id.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
{
"items": [
{
"source": "provider",
"spec": "anthropic/claude-opus-4-8",
"display_name": "Claude Opus 4.8",
"context_window": 200000,
"output_limit": 32000,
"reasoning": true,
"input_price": 15,
"output_price": 75
},
{
"source": "fusion",
"spec": "fusion/balanced-default",
"method": "router",
"members": [
"anthropic/claude-sonnet-4-6",
"openai/gpt-5-mini"
],
"context_window": 128000
}
],
"meta": {
"total": 247,
"page": 1,
"limit": 50
}
}
const page = await client.agent.models.listModelsIterator({ page: 1, limit: 50 });
for await (const model of page) {
console.log(model.spec, model.source);
}

Returns one catalogued model by its full spec (e.g. anthropic/claude-opus-4-8), OR a fusion composite by its fusion/<slug> spec. The spec itself contains a slash; pass it URL-encoded or as the trailing path segment.

This endpoint takes no request body.

NameInTypeRequiredDescription
specpathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
{
"source": "provider",
"spec": "anthropic/claude-opus-4-8",
"display_name": "Claude Opus 4.8",
"context_window": 200000,
"output_limit": 32000,
"reasoning": true,
"input_price": 15,
"output_price": 75
}
const model = await client.agent.models.getModel({ spec: "anthropic/claude-opus-4-8" });

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.

This endpoint takes no request body.

NameInTypeRequiredDescription
pagequeryintegerNo1-based page number for pagination.
limitqueryintegerNoMaximum items per page (0 = no pagination).
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
{
"items": [
{
"id": "anthropic",
"display_name": "Anthropic",
"model_prefix": "anthropic/",
"wire_format": "anthropic-messages",
"model_count": 12
},
{
"id": "openai",
"display_name": "OpenAI",
"model_prefix": "openai/",
"wire_format": "openai-chat",
"model_count": 24
}
],
"meta": {
"total": 18,
"page": 1,
"limit": 50
}
}
const page = await client.agent.models.listProvidersIterator({ limit: 50 });
for await (const provider of page) {
console.log(provider.id, provider.display_name);
}

Returns one provider’s metadata plus its full catalogued model list.

This endpoint takes no request body.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
{
"id": "anthropic",
"display_name": "Anthropic",
"model_prefix": "anthropic/",
"wire_format": "anthropic-messages",
"model_count": 12,
"models": [
{
"spec": "anthropic/claude-opus-4-8",
"display_name": "Claude Opus 4.8",
"context_window": 200000,
"output_limit": 32000,
"reasoning": true
},
{
"spec": "anthropic/claude-sonnet-4-6",
"display_name": "Claude Sonnet 4.6",
"context_window": 200000,
"output_limit": 16000,
"reasoning": false
}
]
}
const provider = await client.agent.models.getProvider({ id: "anthropic" });

Reports a provider’s stored-credential state: whether an API key or OAuth login is stored, the effective default method, the passwordless flag, and the secret-free OAuth account pool. Any stored API key is surfaced as a 10-character prefix only.

This endpoint takes no request body.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
{
"provider_id": "anthropic",
"has_api_key": true,
"api_key_prefix": "sk-ant-api",
"default": "api_key",
"passwordless": false,
"oauth_pool": [
{
"key": "default",
"label": "Personal",
"active": true,
"cooldown": null
}
]
}
const status = await client.agent.models.getProviderAuth({ id: "anthropic" });

These endpoints manage the per-provider OAuth account pool (a list of default and named OAuth logins that the agent can rotate through).

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. Returns 422 oauth_unsupported when the provider has no OAuth flow.

This endpoint takes no request body.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
pagequeryintegerNo1-based page number for pagination.
limitqueryintegerNoMaximum items per page (0 = no pagination).
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
{
"items": [
{
"key": "default",
"label": "Personal",
"active": true,
"cooldown": null
},
{
"key": "work",
"label": "Work account",
"active": false,
"cooldown": "2025-11-10T12:00:00Z"
}
],
"meta": {
"total": 2,
"page": 1,
"limit": 50
}
}
const page = await client.agent.models.listProviderAccountsIterator({ id: "anthropic" });

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 used by startProviderOAuth. Returns 422 oauth_unsupported when the provider has no OAuth flow.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.

This endpoint accepts an empty JSON object as its body. No fields are required.

{}
{
"job_id": "oj_3f2c1a0b9d4e",
"verification_uri": "https://auth.example.com/activate",
"user_code": "ABCD-1234"
}
const job = await client.agent.models.addProviderAccount({ id: "anthropic" });

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; the trailing /active sub-resource names the activation verb. Returns the resulting secret-free account pool. Returns 422 oauth_unsupported when the provider has no OAuth flow.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
keypathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.

This endpoint accepts an empty JSON object as its body. No fields are required; the account is identified by the {key} path value.

{}
{
"accounts": [
{
"key": "default",
"label": "Personal",
"active": false,
"cooldown": null
},
{
"key": "work",
"label": "Work account",
"active": true,
"cooldown": null
}
]
}
await client.agent.models.setProviderAccountActive({ id: "anthropic", key: "work" });

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. Returns 422 oauth_unsupported when the provider has no OAuth flow.

This endpoint takes no request body.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
keypathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
{
"accounts": [
{
"key": "default",
"label": "Personal",
"active": true,
"cooldown": null
}
]
}
await client.agent.models.removeProviderAccount({ id: "anthropic", key: "work" });

These endpoints drive an interactive OAuth login against a provider. The flow is: startpoll (and optionally submit a manual code) → terminal state. The job id carries only the non-secret verification URL and user code; no token is ever returned.

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. Returns {success, job_id, verification_uri, user_code?}. Drive the flow with GET .../oauth/{job} (poll) and, for a manual/PKCE code, POST .../oauth/{job}/code (submit). Returns 422 oauth_unsupported when the provider has no OAuth flow.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
NameTypeRequiredDescription
add_accountbooleanNoWhen true, the login adds to the provider’s OAuth account pool (LoginAddAccount) instead of replacing the primary login.
{
"job_id": "oj_8a7b6c5d4e3f",
"verification_uri": "https://auth.example.com/activate",
"user_code": "WXYZ-9876"
}
const job = await client.agent.models.startProviderOAuth({ id: "anthropic" });

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).

This endpoint takes no request body.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
jobpathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
{
"state": "pending",
"verification_uri": "https://auth.example.com/activate",
"user_code": "WXYZ-9876",
"needs_code": false,
"error": null
}
const poll = await client.agent.models.pollProviderOAuth({ id: "anthropic", job: "oj_8a7b6c5d4e3f" });
if (poll.state === "complete") { /* authorized */ }

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 — the explicit code-exchange step for manual-paste / PKCE flows. A second submit on an already-fed job is a no-op (submitted: false). Returns 404 job_not_found when the job is unknown or expired.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
jobpathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
NameTypeRequiredDescription
codestringYesThe authorization code (or the full redirect URL) to complete the exchange.
{
"submitted": true,
"message": "code accepted; flow is completing"
}
await client.agent.models.submitProviderOAuthCode({
id: "anthropic",
job: "oj_8a7b6c5d4e3f",
data: { code: "splx-auth-code-12345" }
});

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. Returns 422 oauth_unsupported when the provider has no OAuth flow.

This endpoint takes no request body.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
{
"auth": {
"provider_id": "anthropic",
"has_api_key": true,
"api_key_prefix": "sk-ant-api",
"default": "api_key",
"passwordless": false,
"oauth_pool": []
}
}
await client.agent.models.logoutProviderOAuth({ id: "anthropic" });

These endpoints store, set, and remove a provider’s API key in the 0600-atomic ~/.hoody/.env keychainless store. The reply never returns the key — it echoes only a secret-free auth status (10-character prefix).

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. The key is written 0600-atomic and is never returned; the reply carries only the secret-free auth status (10-char prefix). Returns 422 auth_method_unsupported for a passwordless provider.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
NameTypeRequiredDescription
api_keystringYesThe provider API key. Stored in the 0600 ~/.hoody/.env store; the reply echoes only a prefix.
{
"auth": {
"provider_id": "anthropic",
"has_api_key": true,
"api_key_prefix": "sk-ant-api",
"default": "api_key",
"passwordless": false,
"oauth_pool": []
}
}
await client.agent.models.setProviderAPIKey({
id: "anthropic",
data: { api_key: "sk-ant-api03-..." }
});

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.

This endpoint takes no request body.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
{
"auth": {
"provider_id": "anthropic",
"has_api_key": false,
"api_key_prefix": null,
"default": null,
"passwordless": false,
"oauth_pool": []
}
}
await client.agent.models.deleteProviderAPIKey({ id: "anthropic" });

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 the method against the provider’s capabilities and that a credential of that kind is actually stored, otherwise returns 422.

NameInTypeRequiredDescription
idpathstringYesPath identifier.
X-Hoody-CwdheaderstringNoPer-request working-directory scope.
X-Hoody-Config-DirheaderstringNoPer-request --config-dir override.
X-Hoody-ContainerheaderstringNoPer-request bound remote container.
X-Hoody-RealmheaderstringNoPer-request realm selector.
realmquerystringNoQuery alias of the X-Hoody-Realm header.
NameTypeRequiredDescription
defaultstringYesThe default method: api_key or oauth. Must be a method the provider supports AND has a stored credential for.
{
"auth": {
"provider_id": "anthropic",
"has_api_key": true,
"api_key_prefix": "sk-ant-api",
"default": "api_key",
"passwordless": false,
"oauth_pool": [
{
"key": "default",
"label": "Personal",
"active": true,
"cooldown": null
}
]
}
}
await client.agent.models.setProviderDefault({
id: "anthropic",
data: { default: "api_key" }
});