# advise

LLM-backed advisor — the agent-callable action plus admin config and metering.

**Amfelt REST API** · v1.13.0 · `https://api.amfelt.com`

---
### GET `/v1/advise/usage` {#listadviseusage}

**List LLM-usage events.**

Returns LLM-usage rows for the caller's organization, newest first, with the page's aggregate cost.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |
| `env` | query | No | string | DTAP environment to scope the request to; defaults server-side to the api's configured environment. |
| `cursor` | query | No | string | Opaque cursor for the next page; pass back the 'next_cursor' from a prior response. |
| `limit` | query | No | integer | Maximum number of items to return per page. |

#### Responses

**200** — A page of LLM-usage events with the page's total cost.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [LlmUsage](#llm-usage)[] | Yes | The page of usage rows. |
| `next_cursor` | [string null] | No | Opaque cursor for the next page, or null when this is the final page. Examples: `eyJpZCI6OTk5fQ`. |
| `total_cost_usd` | number | Yes | Sum of cost_usd across the returned rows; per-page aggregate. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/advise/providers` {#listadvisorproviders}

**List the providers and models an own key may use.**

Returns the providers an own key may be registered for, with the models each accepts and whether it needs a base_url on this deployment, and the provider and model of the caller's organization's calls on the Amfelt platform key. The provider list is the server-side allowlist that setAdvisorOwnKey and updateAdviseConfig validate against, so a client offers only values they accept. It names no key and reads no vault. Every role reads it.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |

#### Responses

**200** — The own-key providers and the platform key's provider and model.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [AdvisorProvider](#advisor-provider)[] | Yes | The providers an own key may be registered for. |
| `platform_model` | string | Yes | The model of the caller's organization's calls on the platform key: LlmConfig.model while provider_source is platform. Named whether or not this deployment has a platform key configured; without one, a call on the platform key answers no_answer with reason no_provider. Examples: `claude-sonnet-5-5`. |
| `platform_provider` | string | Yes | The provider of the Amfelt platform key's calls. Examples: `anthropic`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/advise/notice/dismiss` {#dismissadvisornotice}

**Dismiss the one-time advisor invitation for the caller.**

Records that the caller dismissed the advisor invitation in the caller's organization. Dismissing chooses nothing: the advisor stays off. Idempotent: a second dismissal keeps the first dismissed_at.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |

#### Responses

**200** — The invitation state after the dismissal; show is false.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `dismissed_at` | [string null] | Yes | When the caller dismissed the invitation. Always present on the wire; null while not dismissed. |
| `show` | boolean | Yes | Whether the app shows the invitation to the caller now: true only for an owner or admin of an organization with no recorded choice at all (never switched on or off), who has not dismissed it. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/advise/notice` {#getadvisornotice}

**Get whether to show the caller the one-time advisor invitation.**

The one-time invitation to opt in, for owners and admins of an organization with no recorded choice at all: setAdvisorEnabled was never called for it, on or off (enabled_changed_at is null), as for one that existed before onboarding asked. An organization that declined, at onboarding or later, has a recorded off choice and is never invited. The invitation tells them the advisor is off, what switching it on sends and costs, and links to the advisor settings; it never switches the advisor on. The record is per account and organization, so a dismissal holds across devices. show is true only for an owner or admin of an organization with no recorded choice who has not dismissed it.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |

#### Responses

**200** — Whether to show the invitation to the caller.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `dismissed_at` | [string null] | Yes | When the caller dismissed the invitation. Always present on the wire; null while not dismissed. |
| `show` | boolean | Yes | Whether the app shows the invitation to the caller now: true only for an owner or admin of an organization with no recorded choice at all (never switched on or off), who has not dismissed it. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PUT `/v1/advise/enabled` {#setadvisorenabled}

**Switch the organization's advisor on or off.**

Sets whether the advisor may be asked for the caller's organization, in every environment and on either key. The advisor is off by default: this operation is the only way an organization becomes enabled, and it is the explicit opt-in that authorizes provider calls (api#1237). An owner or admin may switch it on or off (org.llm-config.update); operator and viewer cannot. The onboarding step and the Advisor tab both make it, and it records the caller as enabled_changed_by, the time, the disclosure_version shown with the choice, and where it was made (reason, read back as enabled_reason). Off is a choice too: continuing onboarding with the advisor box unchecked sends enabled false through this operation, so the organization has a recorded choice and is never invited (getAdvisorNotice). It changes no environment's provider settings and no own key. With the advisor off, no advisor call leaves the organization: every advise answers no_answer with reason disabled.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `disclosure_version` | string | Yes | The version of the disclosure the person saw with the choice: what the advisor sends to Claude (Anthropic) and the free allowance. The api records it with the choice and returns it on reads. Examples: `advisor-disclosure-2026-10-17`. |
| `enabled` | boolean | Yes | Switches the advisor on (true) or off (false) for the whole organization. |
| `reason` | [AdvisorEnabledReason](#advisor-enabled-reason) | No |  |


#### Responses

**200** — The organization's advisor switch after the save.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `disclosure_version` | [string null] | Yes | The version of the disclosure shown with the last choice; null when the organization never chose. Examples: `advisor-disclosure-2026-10-17`. |
| `enabled` | boolean | Yes | Whether the advisor may be asked for this organization, on either key. |
| `enabled_changed_at` | [string null] | Yes | When the organization's advisor was last switched on or off; null when never. |
| `enabled_changed_by` |  | Yes | The account that last switched the organization's advisor on or off; null when the organization never chose. |
| `enabled_reason` |  | Yes | Where the last choice was made: onboarding or settings (AdvisorEnabledUpdate.reason); null when the organization never chose. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/advise/config` {#getadviseconfig}

**Get the current LLM advisor config.**

Returns the advisor settings for the caller's organization in the requested environment: whether the advisor is on, which key that environment's calls run on (provider_source, derived from whether an own key is registered there, never saved), the provider and model, whether an own key is registered (never the key), and who last changed the settings and when. Every role reads them; only an owner or admin changes them (setAdvisorEnabled, updateAdviseConfig, setAdvisorOwnKey, removeAdvisorOwnKey). A read never creates or enables settings: an organization that never saved them reads enabled false and a null enabled_changed_at. enabled is one value for the organization; the provider settings are those of the requested environment.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |
| `env` | query | No | string | DTAP environment to scope the request to; defaults server-side to the api's configured environment. |

#### Responses

**200** — The current LLM config.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `base_url` | string | No | The own key's OpenAI-compatible gateway base URL (ADR-091), as saved. Only meaningful for provider `gateway`; absent while provider_source is platform. A default gateway exists only where this deployment pins a gateway base URL: there an empty value uses the pinned one, and the pinned one wins over a saved value. A deployment that pins none refuses an empty base_url for provider `gateway` when an own key is registered (setAdvisorOwnKey). Examples: `http://litellm:4000/v1`. |
| `changed_at` | [string null] | No | When this environment's own key or provider settings last changed. Always present on the wire; null when never. |
| `changed_by` |  | No | The account that last changed this environment's own key or its provider settings (setAdvisorOwnKey, removeAdvisorOwnKey, updateAdviseConfig). Always present on the wire; null when none was ever changed. |
| `disclosure_version` | [string null] | No | The version of the disclosure shown to the account that last switched the advisor on or off (AdvisorEnabledUpdate). Always present on the wire; null when the organization never chose. Examples: `advisor-disclosure-2026-10-17`. |
| `enabled` | boolean | Yes | Whether the advisor may be asked for this organization. One value for the whole organization, the same in every environment's read; the provider settings below are per environment. Off by default: false for an organization that never chose, and only setAdvisorEnabled by an owner or admin turns it on. No missing row, registered key, allowance or provider default reads as true. When false, no advisor call leaves the organization on either key. |
| `enabled_changed_at` | [string null] | No | When the organization's advisor was last switched on or off. Always present on the wire; null when never. |
| `enabled_changed_by` |  | No | The account that last switched the organization's advisor on or off. Always present on the wire; null when the organization never chose. |
| `enabled_reason` |  | No | Where the advisor was last switched on or off: onboarding or settings (AdvisorEnabledUpdate.reason). Always present on the wire; null when the organization never chose. |
| `env` | string | Yes | DTAP environment this config applies to. Examples: `production`. |
| `model` | string | Yes | The LLM model this environment's calls run on: the own key's saved model while provider_source is own_key, the organization's platform-key model while it is platform. Examples: `claude-sonnet-5-5`. |
| `own_key` | [AdvisorOwnKeyStatus](#advisor-own-key-status) | Yes |  |
| `provider` | string | Yes | The LLM provider this environment's calls run on: the own key's while provider_source is own_key, the platform key's while it is platform (e.g. anthropic, openai, gateway for an OpenAI-compatible gateway endpoint). Examples: `anthropic`. |
| `provider_source` | [AdvisorProviderSource](#advisor-provider-source) | Yes |  |
| `vault_ref` | string | Yes | Informational vault path where the organization's own LLM API key is (or would be) stored for this environment and provider. The Amfelt platform key is never stored in an organization vault. Examples: `production/llm/anthropic`. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PUT `/v1/advise/config` {#updateadviseconfig}

**Update the LLM advisor config.**

Upserts the saved provider settings of one environment for the caller's organization: provider, model and base_url, for the environment env names. Provider and model are validated against the server-side allowlist (listAdvisorProviders). The save records the caller as changed_by. It never switches the advisor on or off (setAdvisorEnabled does), and it never changes which key a call runs on, nor an own key's settings.

While an own key is registered in the environment, that key's provider, model and base_url change only through setAdvisorOwnKey: a body whose provider, model or base_url differs from them answers 409 (code conflict) and changes nothing, so this operation cannot move the environment's calls off its own key by naming another provider. A body equal to them answers 200 with nothing changed. While no own key is registered, the environment's calls run on the platform key with the platform key's provider and the organization's platform model (AdvisorProviderList.platform_provider and platform_model, LlmConfig.model) whatever this saves, and no call uses the saved settings. base_url is refused with 400 when no own key is registered, since only an own key's calls use it.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `base_url` | string | No | Optional OpenAI-compatible gateway base URL (ADR-091) of the own key. Only meaningful for provider `gateway`. A default gateway exists only where this deployment pins a gateway base URL: there an empty or absent value uses the pinned one, and the pinned one wins over the value saved here. A deployment that pins none has no default gateway, so there an own key on provider `gateway` always has a base_url (setAdvisorOwnKey refuses an empty one). Refused with 400 when no own key is registered in this environment: only an own key's calls use it. While one is registered it must equal that key's base_url (409 otherwise). Examples: `http://litellm:4000/v1`. |
| `env` | string | No | DTAP environment; defaults server-side to the api's configured environment. Examples: `production`. |
| `model` | string | Yes | LLM model; validated against the server-side allowlist for the provider. Examples: `claude-sonnet-4-6`. |
| `provider` | string | Yes | LLM provider; validated against the server-side allowlist (e.g. anthropic, openai, gateway for an OpenAI-compatible gateway endpoint). Examples: `anthropic`. |


#### Responses

**200** — The updated LLM config.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `base_url` | string | No | The own key's OpenAI-compatible gateway base URL (ADR-091), as saved. Only meaningful for provider `gateway`; absent while provider_source is platform. A default gateway exists only where this deployment pins a gateway base URL: there an empty value uses the pinned one, and the pinned one wins over a saved value. A deployment that pins none refuses an empty base_url for provider `gateway` when an own key is registered (setAdvisorOwnKey). Examples: `http://litellm:4000/v1`. |
| `changed_at` | [string null] | No | When this environment's own key or provider settings last changed. Always present on the wire; null when never. |
| `changed_by` |  | No | The account that last changed this environment's own key or its provider settings (setAdvisorOwnKey, removeAdvisorOwnKey, updateAdviseConfig). Always present on the wire; null when none was ever changed. |
| `disclosure_version` | [string null] | No | The version of the disclosure shown to the account that last switched the advisor on or off (AdvisorEnabledUpdate). Always present on the wire; null when the organization never chose. Examples: `advisor-disclosure-2026-10-17`. |
| `enabled` | boolean | Yes | Whether the advisor may be asked for this organization. One value for the whole organization, the same in every environment's read; the provider settings below are per environment. Off by default: false for an organization that never chose, and only setAdvisorEnabled by an owner or admin turns it on. No missing row, registered key, allowance or provider default reads as true. When false, no advisor call leaves the organization on either key. |
| `enabled_changed_at` | [string null] | No | When the organization's advisor was last switched on or off. Always present on the wire; null when never. |
| `enabled_changed_by` |  | No | The account that last switched the organization's advisor on or off. Always present on the wire; null when the organization never chose. |
| `enabled_reason` |  | No | Where the advisor was last switched on or off: onboarding or settings (AdvisorEnabledUpdate.reason). Always present on the wire; null when the organization never chose. |
| `env` | string | Yes | DTAP environment this config applies to. Examples: `production`. |
| `model` | string | Yes | The LLM model this environment's calls run on: the own key's saved model while provider_source is own_key, the organization's platform-key model while it is platform. Examples: `claude-sonnet-5-5`. |
| `own_key` | [AdvisorOwnKeyStatus](#advisor-own-key-status) | Yes |  |
| `provider` | string | Yes | The LLM provider this environment's calls run on: the own key's while provider_source is own_key, the platform key's while it is platform (e.g. anthropic, openai, gateway for an OpenAI-compatible gateway endpoint). Examples: `anthropic`. |
| `provider_source` | [AdvisorProviderSource](#advisor-provider-source) | Yes |  |
| `vault_ref` | string | Yes | Informational vault path where the organization's own LLM API key is (or would be) stored for this environment and provider. The Amfelt platform key is never stored in an organization vault. Examples: `production/llm/anthropic`. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — Code conflict: an own key is registered in the environment and the body's provider, model or base_url differs from that key's. Change them with setAdvisorOwnKey. Nothing is changed.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/advise/calls` {#listadvisorcalls}

**List the organization's recent advisor calls.**

Returns the advisor call log of the caller's organization only (the organization X-Tenant-Slug names, which the caller must be a member of), newest first: per call the object it was about, its environment, the result (auto_applied, awaiting_approval, no_answer, or null while no classification is stored with the call's id), its tokens, and whether it was metered against the allowance. Every role reads it. The call log holds a row only for an advise that passed postAdvise step 2 and was answered with outcome answered (a cache hit included) or with a planned no-answer with reason no_provider, allowance_exhausted or own_key_failed. An advise of a disabled organization (reason disabled) and a control-plane failure (5xx) write no row, and neither does a request refused with 403.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |
| `cursor` | query | No | string | Opaque cursor for the next page; pass back the 'next_cursor' from a prior response. |
| `limit` | query | No | integer | Maximum number of items to return per page. |

#### Responses

**200** — A page of advisor calls, newest first.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [AdvisorCall](#advisor-call)[] | Yes | The page of calls. |
| `next_cursor` | [string null] | No | Opaque cursor for the next page, or null when this is the final page. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/advise/allowance` {#getadvisorallowance}

**Get the organization's free advisor token allowance.**

Returns the caller's organization's free token allowance on the Amfelt platform key: the allowance, the tokens used against it, what is left, and how many calls were metered. The allowance is one value for the organization, shared by every environment, so this read takes no environment. Only calls on the platform key count; calls on the organization's own key are logged but never metered. When nothing is left, an advise call that reaches postAdvise step 5 on the platform key answers no_answer with reason allowance_exhausted, and its table stops after staging and waits for classification (awaiting_classification). The earlier steps still answer first: a disabled organization's call answers no_answer with reason disabled and its table does not wait (step 1), and cached advice is still served, at no cost to the allowance (step 4). Registering an own key for the environment, or raising the allowance, lets a later call reach a provider. Every role reads it.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |

#### Responses

**200** — The organization's allowance.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `allowance_tokens` | integer | Yes | The allowance. Set per organization by a sysadmin (setOrgAdvisorAllowance). Examples: `500000`. |
| `metered_calls` | integer | Yes | The number of calls metered against the allowance; with used_tokens it gives the average tokens per call, from which a client can estimate the tables the remainder covers. Examples: `40`. |
| `remaining_tokens` | integer | Yes | allowance_tokens minus used_tokens, and never below 0. Examples: `380000`. |
| `used_tokens` | integer | Yes | Tokens metered against the allowance so far. Calls already in flight when the allowance runs out are still metered, so this may exceed allowance_tokens. Examples: `120000`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/advise` {#postadvise}

**Request advice.**

Submits a contract and TOON profile to the configured LLM and returns the advisor's response. Agent-callable, and only by an agent: the api authenticates the call with the calling agent's JWT (agent-auth), never with a member's token, so no member can call it, and every check below reads the calling agent. It carries no customer permission. It stores no classification and changes no configuration: the agent files an answer through ingestCatalogClassifications. It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus).

The api decides each request in this order, and a step that answers ends the request:

1. Enablement. The advisor must be enabled for the organization. It is off until an owner or admin switches it on (setAdvisorEnabled); an organization that never chose is off. The api checks this first, before it returns cached advice, resolves either key, selects a provider or calls any LLM. When it is off, nothing is sent, nothing cached is returned and no call-log row is written: no_answer with reason disabled. Disabled is a normal product path, not a wait: an agent built on this contract then loads the object's history in its resolved history mode (the job's, or the default) and does not leave it waiting for classification.
2. Environment and assignment. The call resolves in the environment the request names (AdviseRequest.environment) or, when it names none, in the api's configured environment. Named or not, that environment must be a project environment whose runs dispatch to the calling agent (setProjectEnvironmentAgent), and, when the request names project_id, an environment of that project. Otherwise the api answers 403 (code permission_denied): nothing is sent, nothing cached is returned and no call-log row is written.
3. Settings. The environment's settings decide which key the call runs on (LlmConfig.provider_source): its own key while one is registered there (at {env}/llm/{provider}, see setAdvisorOwnKey), the Amfelt platform key otherwise, each with its provider and model, and an own key on provider gateway with its base URL (LlmConfig.base_url). An own key on provider gateway with no usable base URL answers no_answer with reason own_key_failed.
4. Cache. Cached advice is served only at this step, so only to an enabled organization and an assigned agent: a cached answer for the same organization, provider settings, contract and profile answers outcome answered with cache_hit true and 0 tokens. A cache hit asks no provider, is never metered and costs no allowance, so it is served even when the allowance is used up. It writes a call-log row like any answered call, so its answer can be filed with advisor_call_id.
5. Key and provider. On the own key, the api reads the key from the environment's vault (503 when it cannot) and asks its provider; the call is never metered. When the provider refuses or fails on that key (an invalid key, its quota, an outage or a timeout of its provider), the answer is no_answer with reason own_key_failed; the platform key is never used in its place. On the platform key, the api checks the allowance before any provider call: no_answer with reason allowance_exhausted when none is left, and with reason no_provider when this deployment has no platform key configured. Otherwise it asks the platform key's provider, and the call is metered.

A no_answer is a 200: the api sent nothing to a provider, or its own key failed. It is a designed answer, not an outage, and the agent stores no classification. With reason disabled the object does not wait (step 1). With reason own_key_failed, allowance_exhausted or no_provider, as after an outage, the object stops after staging and waits for classification; an unsure answer waits too, for approval of its proposal. Only failures on the control plane's side are 5xx: reading the own key from the organization vault (503), the platform key's provider (502, and 504 on its timeout), and internal errors (500). A 5xx writes no call-log row; the call log holds a row only for a request that passed step 2 and was answered with outcome answered or with a no_answer other than disabled (listAdvisorCalls).

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `contract_yaml` | string | Yes | ODCS contract YAML; capped at 512 KB server-side. |
| `environment` | string | No | The DTAP environment of the run that asks. It selects the environment whose own key and provider settings resolve the call (postAdvise), and is recorded on the call's row in the advisor call log. Optional for compatibility: absent, the api's configured environment resolves the call, as it did before this field. Named or not, the environment the call resolves in must be a project environment whose runs dispatch to the calling agent (setProjectEnvironmentAgent). This is postAdvise step 2, after enablement: a disabled organization's call answers 200 no_answer with reason disabled at step 1, before this check. An enabled organization's call that fails it answers 403 (code permission_denied) and sends nothing. An agent built on this contract sends it. Examples: `development`. |
| `feature` | string | No | Calling feature; defaults server-side to "advisor". Examples: `advisor`. |
| `object_name` | string | No | The schema object's ODCS name (schema[].name) the advice is for. Optional. |
| `project_id` | string | No | The control-plane project of the run that asks. Optional; with environment, source_slug and object_name it names the object on the call's row in the advisor call log. When named, the environment the call resolves in must be an environment of this project whose runs dispatch to the calling agent (postAdvise step 2: 403 otherwise, once step 1 has found the advisor enabled). |
| `source_slug` | string | No | The catalog source of the object the advice is for. Optional. |
| `toon_profile` | string | Yes | TOON profile of the source data; capped at 256 KB server-side. |


#### Responses

**200** — The advice produced by the LLM (outcome answered), or the control plane's typed decision not to ask one (outcome no_answer, with no_answer_reason).

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `cache_hit` | boolean | Yes | Whether the response was served from the advisor cache. |
| `call_id` | string | No | The call's row in the advisor call log. Absent when no row is written: with reason disabled (see AdvisorCall for which calls are logged). Present on a cache hit. The agent passes it back as advisor_call_id when it files the classification, which sets the row's result. |
| `input_tokens` | integer | Yes | Number of input tokens consumed. |
| `model` | string | Yes | LLM model that produced the advice. Examples: `claude-sonnet-4-6`. |
| `no_answer_reason` | [AdvisorNoAnswerReason](#advisor-no-answer-reason) | No |  |
| `outcome` | [AdviseOutcome](#advise-outcome) | Yes |  |
| `output_tokens` | integer | Yes | Number of output tokens generated. |
| `pcf_yaml` | string | Yes | PCF (pipeline configuration format) YAML produced by the LLM. |
| `provider` | string | Yes | LLM provider that produced the advice. Examples: `anthropic`. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — Code permission_denied, at step 2, so only for an organization whose advisor is enabled (step 1): the environment the call resolves in, the one the request names or the api's configured one when it names none, is not assigned to the calling agent (no project environment whose runs dispatch to it), or is not an environment of the project the request names. Nothing is sent and no call-log row is written.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**500** — An internal error of the control plane. An outage, not a no_answer.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**502** — The platform key's provider answered with an error. An outage, not a no_answer. A failure on the organization's own key is never a 502, a missing or misconfigured gateway base URL of an own key included: it is no_answer with reason own_key_failed.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**503** — The control plane could not read the organization's own key from the environment's vault. An outage, not a no_answer.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**504** — The platform key's provider did not answer in time. An outage, not a no_answer. A timeout on the organization's own key is never a 504: it is no_answer with reason own_key_failed.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/platform/orgs/{slug}/advise/allowance` {#getorgadvisorallowance}

**Get one organization's advisor allowance (platform).**

A platform operation for the sysadmin role (ADR-053), which is never a membership role: no customer role holds a platform.* permission. The organization is named in the path, not by X-Tenant-Slug. Returns the same body as getAdvisorAllowance.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `slug` | path | Yes | string | Slug of the organization. |

#### Responses

**200** — The organization's allowance.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `allowance_tokens` | integer | Yes | The allowance. Set per organization by a sysadmin (setOrgAdvisorAllowance). Examples: `500000`. |
| `metered_calls` | integer | Yes | The number of calls metered against the allowance; with used_tokens it gives the average tokens per call, from which a client can estimate the tables the remainder covers. Examples: `40`. |
| `remaining_tokens` | integer | Yes | allowance_tokens minus used_tokens, and never below 0. Examples: `380000`. |
| `used_tokens` | integer | Yes | Tokens metered against the allowance so far. Calls already in flight when the allowance runs out are still metered, so this may exceed allowance_tokens. Examples: `120000`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PUT `/v1/platform/orgs/{slug}/advise/allowance` {#setorgadvisorallowance}

**Set one organization's advisor allowance (platform).**

A platform operation for the sysadmin role (ADR-053), which is never a membership role: no customer role holds a platform.* permission. Sets the organization's free token allowance on the Amfelt platform key. used_tokens is left as it is, so an allowance below it leaves nothing remaining.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `slug` | path | Yes | string | Slug of the organization. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `allowance_tokens` | integer | Yes | The new allowance in tokens. 0 stops all metered calls. Examples: `1000000`. |


#### Responses

**200** — The organization's allowance after the change.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `allowance_tokens` | integer | Yes | The allowance. Set per organization by a sysadmin (setOrgAdvisorAllowance). Examples: `500000`. |
| `metered_calls` | integer | Yes | The number of calls metered against the allowance; with used_tokens it gives the average tokens per call, from which a client can estimate the tables the remainder covers. Examples: `40`. |
| `remaining_tokens` | integer | Yes | allowance_tokens minus used_tokens, and never below 0. Examples: `380000`. |
| `used_tokens` | integer | Yes | Tokens metered against the allowance so far. Calls already in flight when the allowance runs out are still metered, so this may exceed allowance_tokens. Examples: `120000`. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/advise/own-keys/{environment}/test` {#testadvisorownkey}

**Test the organization's own LLM key in one environment.**

Sends a minimal fixed request to the provider of the own key registered in the environment, with its saved provider, model and base_url, and reports whether the provider answered. It sends no table structure or profile, is not written to the advisor call log, and is never metered. A failed test is a 200 with ok false and a reason, not an error.

A test is allowed while the advisor is off. It sends no customer data at all, only that minimal fixed probe, and the advisor switch (setAdvisorEnabled) governs whether customer data is sent, which only postAdvise does. So a key can be checked before the advisor is switched on, and testing it switches nothing on.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |
| `environment` | path | Yes | string | DTAP environment slug whose vault holds the own key; answers 404 when the organization has no such environment. |

#### Responses

**200** — The test's outcome.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `model` | string | No | The model tested; absent with reason not_set. Examples: `claude-sonnet-5-5`. |
| `ok` | boolean | Yes | Whether the provider answered on the own key. |
| `provider` | string | No | The provider tested; absent with reason not_set. Examples: `anthropic`. |
| `reason` | [AdvisorOwnKeyTestFailure](#advisor-own-key-test-failure) | No |  |
| `tested_at` | string | Yes | When the test ran. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**503** — The control plane could not read the own key from the environment's vault; the provider was not asked.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PUT `/v1/advise/own-keys/{environment}` {#setadvisorownkey}

**Register or replace the organization's own LLM key in one environment.**

Stores the organization's own provider key in the environment's vault at {environment}/llm/{provider}, together with the provider settings its calls use (provider, model, base_url), as one change that records the caller as changed_by. From then on that environment's advise calls run on this key and are never metered. api_key may be left out to keep the key already registered there for the same provider, for example to change only the model. Registering a key for another provider deletes the environment's previous own key, so an environment has at most one. The key is never returned: the response is the environment's settings read, with own_key set. Registering a key does not switch the advisor on. This is the only operation that changes a registered own key's provider, model or base_url; updateAdviseConfig answers 409 rather than change them.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |
| `environment` | path | Yes | string | DTAP environment slug whose vault holds the own key; answers 404 when the organization has no such environment. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `api_key` | string | No | The provider API key. Write-only: stored in the environment's vault and never returned by any customer-facing or advisor operation. Required when the environment has no own key for this provider (400 otherwise); absent keeps that key. Outside this contract and unchanged by it, resolveSecret with the operator-bootstrap token is not limited to particular vault refs (it stays organization-wide, gated by the policy engine), so this key's {environment}/llm/{provider} ref is within its reach. Likewise outside it, registerSecret and updateSecret with the operator-bootstrap token are organization-wide, so they can write a secret at that ref without setAdvisorOwnKey; this contract does not govern such a write. |
| `base_url` | string | No | Optional OpenAI-compatible gateway base URL (ADR-091). Only meaningful for provider `gateway`. A default gateway exists only where this deployment pins a gateway base URL: there an empty or absent value uses the pinned one, and the pinned one wins over the value saved here. A deployment that pins none refuses an empty or absent base_url for provider `gateway` with 400 (AdvisorProvider.requires_base_url). Examples: `http://litellm:4000/v1`. |
| `model` | string | Yes | LLM model; validated against the server-side allowlist for the provider. Examples: `claude-sonnet-5-5`. |
| `provider` | string | Yes | LLM provider; validated against the server-side allowlist (e.g. anthropic, openai, gateway for an OpenAI-compatible gateway endpoint). The key is stored at {environment}/llm/{provider}. Examples: `anthropic`. |


#### Responses

**200** — The environment's advisor settings after the save; never the key.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `base_url` | string | No | The own key's OpenAI-compatible gateway base URL (ADR-091), as saved. Only meaningful for provider `gateway`; absent while provider_source is platform. A default gateway exists only where this deployment pins a gateway base URL: there an empty value uses the pinned one, and the pinned one wins over a saved value. A deployment that pins none refuses an empty base_url for provider `gateway` when an own key is registered (setAdvisorOwnKey). Examples: `http://litellm:4000/v1`. |
| `changed_at` | [string null] | No | When this environment's own key or provider settings last changed. Always present on the wire; null when never. |
| `changed_by` |  | No | The account that last changed this environment's own key or its provider settings (setAdvisorOwnKey, removeAdvisorOwnKey, updateAdviseConfig). Always present on the wire; null when none was ever changed. |
| `disclosure_version` | [string null] | No | The version of the disclosure shown to the account that last switched the advisor on or off (AdvisorEnabledUpdate). Always present on the wire; null when the organization never chose. Examples: `advisor-disclosure-2026-10-17`. |
| `enabled` | boolean | Yes | Whether the advisor may be asked for this organization. One value for the whole organization, the same in every environment's read; the provider settings below are per environment. Off by default: false for an organization that never chose, and only setAdvisorEnabled by an owner or admin turns it on. No missing row, registered key, allowance or provider default reads as true. When false, no advisor call leaves the organization on either key. |
| `enabled_changed_at` | [string null] | No | When the organization's advisor was last switched on or off. Always present on the wire; null when never. |
| `enabled_changed_by` |  | No | The account that last switched the organization's advisor on or off. Always present on the wire; null when the organization never chose. |
| `enabled_reason` |  | No | Where the advisor was last switched on or off: onboarding or settings (AdvisorEnabledUpdate.reason). Always present on the wire; null when the organization never chose. |
| `env` | string | Yes | DTAP environment this config applies to. Examples: `production`. |
| `model` | string | Yes | The LLM model this environment's calls run on: the own key's saved model while provider_source is own_key, the organization's platform-key model while it is platform. Examples: `claude-sonnet-5-5`. |
| `own_key` | [AdvisorOwnKeyStatus](#advisor-own-key-status) | Yes |  |
| `provider` | string | Yes | The LLM provider this environment's calls run on: the own key's while provider_source is own_key, the platform key's while it is platform (e.g. anthropic, openai, gateway for an OpenAI-compatible gateway endpoint). Examples: `anthropic`. |
| `provider_source` | [AdvisorProviderSource](#advisor-provider-source) | Yes |  |
| `vault_ref` | string | Yes | Informational vault path where the organization's own LLM API key is (or would be) stored for this environment and provider. The Amfelt platform key is never stored in an organization vault. Examples: `production/llm/anthropic`. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### DELETE `/v1/advise/own-keys/{environment}` {#removeadvisorownkey}

**Remove the organization's own LLM key from one environment.**

Deletes the organization's own key from the environment's vault (Remove my key on the Advisor tab). From then on that environment's advise calls run on the Amfelt platform key and count against the free allowance. Idempotent: an environment without an own key answers 200 with its settings unchanged.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `X-Tenant-Slug` | header | Yes | string | Slug of the organization to scope the request to; required for user-authenticated requests. |
| `environment` | path | Yes | string | DTAP environment slug whose vault holds the own key; answers 404 when the organization has no such environment. |

#### Responses

**200** — The environment's advisor settings after the removal, with own_key not_set.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `base_url` | string | No | The own key's OpenAI-compatible gateway base URL (ADR-091), as saved. Only meaningful for provider `gateway`; absent while provider_source is platform. A default gateway exists only where this deployment pins a gateway base URL: there an empty value uses the pinned one, and the pinned one wins over a saved value. A deployment that pins none refuses an empty base_url for provider `gateway` when an own key is registered (setAdvisorOwnKey). Examples: `http://litellm:4000/v1`. |
| `changed_at` | [string null] | No | When this environment's own key or provider settings last changed. Always present on the wire; null when never. |
| `changed_by` |  | No | The account that last changed this environment's own key or its provider settings (setAdvisorOwnKey, removeAdvisorOwnKey, updateAdviseConfig). Always present on the wire; null when none was ever changed. |
| `disclosure_version` | [string null] | No | The version of the disclosure shown to the account that last switched the advisor on or off (AdvisorEnabledUpdate). Always present on the wire; null when the organization never chose. Examples: `advisor-disclosure-2026-10-17`. |
| `enabled` | boolean | Yes | Whether the advisor may be asked for this organization. One value for the whole organization, the same in every environment's read; the provider settings below are per environment. Off by default: false for an organization that never chose, and only setAdvisorEnabled by an owner or admin turns it on. No missing row, registered key, allowance or provider default reads as true. When false, no advisor call leaves the organization on either key. |
| `enabled_changed_at` | [string null] | No | When the organization's advisor was last switched on or off. Always present on the wire; null when never. |
| `enabled_changed_by` |  | No | The account that last switched the organization's advisor on or off. Always present on the wire; null when the organization never chose. |
| `enabled_reason` |  | No | Where the advisor was last switched on or off: onboarding or settings (AdvisorEnabledUpdate.reason). Always present on the wire; null when the organization never chose. |
| `env` | string | Yes | DTAP environment this config applies to. Examples: `production`. |
| `model` | string | Yes | The LLM model this environment's calls run on: the own key's saved model while provider_source is own_key, the organization's platform-key model while it is platform. Examples: `claude-sonnet-5-5`. |
| `own_key` | [AdvisorOwnKeyStatus](#advisor-own-key-status) | Yes |  |
| `provider` | string | Yes | The LLM provider this environment's calls run on: the own key's while provider_source is own_key, the platform key's while it is platform (e.g. anthropic, openai, gateway for an OpenAI-compatible gateway endpoint). Examples: `anthropic`. |
| `provider_source` | [AdvisorProviderSource](#advisor-provider-source) | Yes |  |
| `vault_ref` | string | Yes | Informational vault path where the organization's own LLM API key is (or would be) stored for this environment and provider. The Amfelt platform key is never stored in an organization vault. Examples: `production/llm/anthropic`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

