# Agent: Models & Providers

**Page:** api/agent/models

[Download Raw Markdown](./api/agent/models.md)

---

{/* AUTO-GENERATED — Do not edit manually. Regenerate with: npm run docs:api:generate */}



# Agent: Models & Providers

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.


Secret-handling contract: every response that touches credentials is **secret-free**. Stored API keys are surfaced as a 10-character prefix only; OAuth logins carry only the non-secret `verification_uri` and `user_code`. No token is ever returned over the wire.


---

## Models

### `GET /api/v1/agent/models`

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.

### 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. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override selecting which on-disk `.hoody` install a stateless read/write resolves. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container (omitted = local). |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector: `global` or a 24-hex id. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Response



```json
{
  "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
  }
}
```


```json
{
  "code": "realm_scope_unsupported",
  "message": "realm scope not supported on this route"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. |
| `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to a no-realm RPC. | Omit the realm header on this route, or open a session to scope by realm. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`; the source IP is private or local. | Reach the agent through `hoody-proxy` (e.g. `hoody agent …`), not by connecting to the container directly. |


```json
{
  "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. |


```json
{
  "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. |


```json
{
  "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. |


```json
{
  "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. |



### SDK usage

```ts
const page = await client.agent.models.listModelsIterator({ page: 1, limit: 50 });
for await (const model of page) {
  console.log(model.spec, model.source);
}
```

---

### `GET /api/v1/agent/models/{spec}`

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.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `spec` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Response



```json
{
  "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
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route, or open a session to scope by realm. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Honor the `Retry-After` header and retry; reduce the request rate. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
const model = await client.agent.models.getModel({ spec: "anthropic/claude-opus-4-8" });
```

---

## Providers

### `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`.

This endpoint takes no request body.

### 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. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Response



```json
{
  "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
  }
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
const page = await client.agent.models.listProvidersIterator({ limit: 50 });
for await (const provider of page) {
  console.log(provider.id, provider.display_name);
}
```

---

### `GET /api/v1/agent/providers/{id}`

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

This endpoint takes no request body.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Response



```json
{
  "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
    }
  ]
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
const provider = await client.agent.models.getProvider({ id: "anthropic" });
```

---

## Provider Auth Status

### `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 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.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Response



```json
{
  "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
    }
  ]
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "code": "forbidden",
  "message": "request must arrive through the Hoody proxy"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
const status = await client.agent.models.getProviderAuth({ id: "anthropic" });
```

---

## OAuth Account Pool

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`

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.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `page` | query | integer | No | 1-based page number for pagination. |
| `limit` | query | integer | No | Maximum items per page (`0` = no pagination). |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Response



```json
{
  "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
  }
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "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 Unix-socket admin gate refused the request. | Run the daemon with socket auth and no separate admin capability so its own gate allows HTTP requests forwarded from the gateway. |
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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, so an OAuth start/poll/submit/logout or a default=oauth promotion cannot be honored. | Use an API key for this provider, or pick a provider that supports OAuth. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |



### SDK usage

```ts
const page = await client.agent.models.listProviderAccountsIterator({ id: "anthropic" });
```

---

### `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.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Request Body

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

```json
{}
```

### Response



```json
{
  "job_id": "oj_3f2c1a0b9d4e",
  "verification_uri": "https://auth.example.com/activate",
  "user_code": "ABCD-1234"
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "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 Unix-socket admin gate refused the request. | Run the daemon with socket auth and no separate admin capability so its own gate allows HTTP requests forwarded from the gateway. |
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "code": "payload_too_large",
  "message": "request body exceeds the configured size limit"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `payload_too_large` | Payload too large | The request body exceeds the configured size cap (`MaxBodyBytes`). | Reduce the request body below the configured limit (default 8 MiB). |


```json
{
  "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. | Use an API key for this provider, or pick a provider that supports OAuth. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
const job = await client.agent.models.addProviderAccount({ id: "anthropic" });
```

---

### `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.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `key` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Request Body

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

```json
{}
```

### Response



```json
{
  "accounts": [
    {
      "key": "default",
      "label": "Personal",
      "active": false,
      "cooldown": null
    },
    {
      "key": "work",
      "label": "Work account",
      "active": true,
      "cooldown": null
    }
  ]
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "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 Unix-socket admin gate refused the request. | Run the daemon with socket auth and no separate admin capability so its own gate allows HTTP requests forwarded from the gateway. |
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Reduce the request body below the configured limit (default 8 MiB). |


```json
{
  "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. | Use an API key for this provider, or pick a provider that supports OAuth. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
await client.agent.models.setProviderAccountActive({ id: "anthropic", key: "work" });
```

---

### `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.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `key` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Response



```json
{
  "accounts": [
    {
      "key": "default",
      "label": "Personal",
      "active": true,
      "cooldown": null
    }
  ]
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "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 Unix-socket admin gate refused the request. | Run the daemon with socket auth and no separate admin capability so its own gate allows HTTP requests forwarded from the gateway. |
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Use an API key for this provider, or pick a provider that supports OAuth. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
await client.agent.models.removeProviderAccount({ id: "anthropic", key: "work" });
```

---

## OAuth Login Flow

These endpoints drive an interactive OAuth login against a provider. The flow is: `start` → `poll` (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`

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.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `add_account` | boolean | No | When `true`, the login adds to the provider's OAuth account pool (`LoginAddAccount`) instead of replacing the primary login. |

### Response



```json
{
  "job_id": "oj_8a7b6c5d4e3f",
  "verification_uri": "https://auth.example.com/activate",
  "user_code": "WXYZ-9876"
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "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 Unix-socket admin gate refused the request. | Run the daemon with socket auth and no separate admin capability so its own gate allows HTTP requests forwarded from the gateway. |
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Reduce the request body below the configured limit (default 8 MiB). |


```json
{
  "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. | Use an API key for this provider, or pick a provider that supports OAuth. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
const job = await client.agent.models.startProviderOAuth({ id: "anthropic" });
```

---

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


A **failed** login is reported under the `200` body's `state:"error"`, NOT as an HTTP error. Inspect the `state` field (`pending` | `complete` | `error`), never the HTTP status, to decide success.


This endpoint takes no request body.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `job` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Response



```json
{
  "state": "pending",
  "verification_uri": "https://auth.example.com/activate",
  "user_code": "WXYZ-9876",
  "needs_code": false,
  "error": null
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "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 Unix-socket admin gate refused the request. | Run the daemon with socket auth and no separate admin capability so its own gate allows HTTP requests forwarded from the gateway. |
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Use an API key for this provider, or pick a provider that supports OAuth. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
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`

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.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `job` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `code` | string | Yes | The authorization code (or the full redirect URL) to complete the exchange. |

### Response



```json
{
  "submitted": true,
  "message": "code accepted; flow is completing"
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "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 Unix-socket admin gate refused the request. | Run the daemon with socket auth and no separate admin capability so its own gate allows HTTP requests forwarded from the gateway. |
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Reduce the request body below the configured limit (default 8 MiB). |


```json
{
  "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. | Use an API key for this provider, or pick a provider that supports OAuth. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
await client.agent.models.submitProviderOAuthCode({
  id: "anthropic",
  job: "oj_8a7b6c5d4e3f",
  data: { code: "splx-auth-code-12345" }
});
```

---

### `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.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Response



```json
{
  "auth": {
    "provider_id": "anthropic",
    "has_api_key": true,
    "api_key_prefix": "sk-ant-api",
    "default": "api_key",
    "passwordless": false,
    "oauth_pool": []
  }
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "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 Unix-socket admin gate refused the request. | Run the daemon with socket auth and no separate admin capability so its own gate allows HTTP requests forwarded from the gateway. |
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Use an API key for this provider, or pick a provider that supports OAuth. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
await client.agent.models.logoutProviderOAuth({ id: "anthropic" });
```

---

## API Key Management

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`

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.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Request Body

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `api_key` | string | Yes | The provider API key. Stored in the 0600 `~/.hoody/.env` store; the reply echoes only a prefix. |

### Response



```json
{
  "auth": {
    "provider_id": "anthropic",
    "has_api_key": true,
    "api_key_prefix": "sk-ant-api",
    "default": "api_key",
    "passwordless": false,
    "oauth_pool": []
  }
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "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 Unix-socket admin gate refused the request. | Run the daemon with socket auth and no separate admin capability so its own gate allows HTTP requests forwarded from the gateway. |
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Reduce the request body below the configured limit (default 8 MiB). |


```json
{
  "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. | 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 (e.g. 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. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
await client.agent.models.setProviderAPIKey({
  id: "anthropic",
  data: { api_key: "sk-ant-api03-..." }
});
```

---

### `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.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

### Response



```json
{
  "auth": {
    "provider_id": "anthropic",
    "has_api_key": false,
    "api_key_prefix": null,
    "default": null,
    "passwordless": false,
    "oauth_pool": []
  }
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "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 Unix-socket admin gate refused the request. | Run the daemon with socket auth and no separate admin capability so its own gate allows HTTP requests forwarded from the gateway. |
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | 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. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
await client.agent.models.deleteProviderAPIKey({ id: "anthropic" });
```

---

## Default Credential Method

### `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`.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `id` | path | string | Yes | Path identifier. |
| `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. |
| `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. |
| `X-Hoody-Container` | header | string | No | Per-request bound remote container. |
| `X-Hoody-Realm` | header | string | No | Per-request realm selector. |
| `realm` | query | string | No | Query alias of the `X-Hoody-Realm` header. |

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

### Response



```json
{
  "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
      }
    ]
  }
}
```


```json
{
  "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 a no-realm RPC. | Omit the realm header on this route. |


```json
{
  "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 Unix-socket admin gate refused the request. | Run the daemon with socket auth and no separate admin capability so its own gate allows HTTP requests forwarded from the gateway. |
| `forbidden` | Forbidden (not via the Hoody proxy) | The request did not arrive through `hoody-proxy`. | Reach the agent through `hoody-proxy`. |


```json
{
  "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. |


```json
{
  "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. | Reduce the request body below the configured limit (default 8 MiB). |


```json
{
  "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. | 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 (`PUT /providers/{id}/auth/api-key`, or complete `POST /providers/{id}/auth/oauth`), then set the default. |


```json
{
  "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. | Honor the `Retry-After` header and retry. |


```json
{
  "code": "internal_error",
  "message": "internal server error"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `internal_error` | Internal error | An unexpected error occurred. | Retry; if persistent, inspect the daemon logs. |


```json
{
  "code": "service_unavailable",
  "message": "service unavailable"
}
```

| Error Code | Title | Description | Resolution |
|------------|-------|-------------|------------|
| `service_unavailable` | Service unavailable | The daemon could not service the request. | Honor `Retry-After` and retry. |



### SDK usage

```ts
await client.agent.models.setProviderDefault({
  id: "anthropic",
  data: { default: "api_key" }
});
```