Skip to content

AI models ​

Routes are relative to <PUBLIC_BASE_URL>; request and response bodies are JSON unless stated otherwise. Authentication, scopes, pagination and the error format are described in the REST API guides.

GET /api/v1/llm-defaults ​

Read the resolved AI defaults

The model each purpose will actually use on this instance, after the administrator’s routing. null for a purpose no provider can serve. Model names only: no key, no catalogue.

Access — Member session or API key with scope connections:read.

Responses

200 — The defaults.

FieldTypeRequired
defaultsobjectyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "defaults": {
      "type": "object",
      "properties": {
        "classify": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        },
        "compose": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        },
        "extract": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        },
        "general": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        },
        "analyzer": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        },
        "assistant": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        }
      },
      "required": [ "classify", "compose", "extract", "general", "analyzer", "assistant" ],
      "additionalProperties": false
    }
  },
  "required": [ "defaults" ],
  "additionalProperties": false
}

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

GET /api/v1/llm-connections ​

List the AI connections

What a node selector needs: id, label, provider and default flag. Built without decrypting anything.

Access — Member session or API key with scope connections:read.

Responses

200 — The connections.

FieldTypeRequired
connectionsobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "connections": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "provider": {
            "type": "string",
            "enum": [
              "anthropic",
              "openai",
              "mistral",
              "openrouter",
              "ollama",
              "openai_compatible"
            ]
          },
          "label": { "type": "string" },
          "isDefault": { "type": "boolean" }
        },
        "required": [ "id", "provider", "label", "isDefault" ],
        "additionalProperties": false
      }
    }
  },
  "required": [ "connections" ],
  "additionalProperties": false
}

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

GET /api/v1/llm-connections/{id}/models ​

List the models of an AI connection

restricted says whether the administrator closed the list (only these models) or whether it is a typing aid (known models, others still valid).

Access — Member session or API key with scope connections:read.

Parameters

NameInTypeRequired
idpathstringyes

Responses

200 — The models.

FieldTypeRequired
connectionIdstring (uuid)yes
provider"anthropic" | "openai" | "mistral" | "openrouter" | "ollama" | "openai_compatible"yes
modelsstring[]yes
restrictedbooleanyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "connectionId": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "provider": {
      "type": "string",
      "enum": [
        "anthropic",
        "openai",
        "mistral",
        "openrouter",
        "ollama",
        "openai_compatible"
      ]
    },
    "models": { "type": "array", "items": { "type": "string" } },
    "restricted": { "type": "boolean" }
  },
  "required": [ "connectionId", "provider", "models", "restricted" ],
  "additionalProperties": false
}

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — llm.connection_not_found. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

GET /api/v1/admin/llm/connections ​

List every AI connection

Access — Member session or API key with scope admin:read (administrator role required).

Responses

200 — The connections.

FieldTypeRequired
connectionsobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "connections": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "provider": {
            "type": "string",
            "enum": [
              "anthropic",
              "openai",
              "mistral",
              "openrouter",
              "ollama",
              "openai_compatible"
            ]
          },
          "label": { "type": "string" },
          "isDefault": { "type": "boolean" },
          "hasApiKey": { "type": "boolean" },
          "apiKeyHint": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "baseUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "enabledModels": {
            "anyOf": [
              { "type": "array", "items": { "type": "string" } },
              { "type": "null" }
            ]
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "id",
          "provider",
          "label",
          "isDefault",
          "hasApiKey",
          "apiKeyHint",
          "baseUrl",
          "enabledModels",
          "updatedAt"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [ "connections" ],
  "additionalProperties": false
}

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

POST /api/v1/admin/llm/connections ​

Add an AI connection

Adds a key next to the others for a provider (the provider PUT replaces the default one instead). The label is unique per instance. Recorded in the audit log, never with the key.

Access — Member session or API key with scope admin:write (administrator role required).

Request body (application/json)

FieldTypeRequired
provider"anthropic" | "openai" | "mistral" | "openrouter" | "ollama" | "openai_compatible"yes
labelstringyes
apiKeystringno
baseUrlstring (uri)no
withoutApiKeybooleanno
enabledModelsstring[]no
makeDefaultbooleanno
JSON Schema
json
{
  "type": "object",
  "properties": {
    "provider": {
      "type": "string",
      "enum": [
        "anthropic",
        "openai",
        "mistral",
        "openrouter",
        "ollama",
        "openai_compatible"
      ]
    },
    "label": { "type": "string", "minLength": 1, "maxLength": 80 },
    "apiKey": { "type": "string", "minLength": 1, "maxLength": 512 },
    "baseUrl": { "type": "string", "maxLength": 2048, "format": "uri" },
    "withoutApiKey": { "type": "boolean" },
    "enabledModels": {
      "minItems": 1,
      "maxItems": 64,
      "type": "array",
      "items": { "type": "string", "minLength": 1, "maxLength": 128 }
    },
    "makeDefault": { "type": "boolean" }
  },
  "required": [ "provider", "label" ]
}

Responses

201 — The connection state.

FieldTypeRequired
idstring (uuid)yes
provider"anthropic" | "openai" | "mistral" | "openrouter" | "ollama" | "openai_compatible"yes
labelstringyes
isDefaultbooleanyes
hasApiKeybooleanyes
apiKeyHintstring | nullyes
baseUrlstring | nullyes
enabledModelsstring[] | nullyes
updatedAtstring (date-time)yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
    },
    "provider": {
      "type": "string",
      "enum": [
        "anthropic",
        "openai",
        "mistral",
        "openrouter",
        "ollama",
        "openai_compatible"
      ]
    },
    "label": { "type": "string" },
    "isDefault": { "type": "boolean" },
    "hasApiKey": { "type": "boolean" },
    "apiKeyHint": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
    "baseUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
    "enabledModels": {
      "anyOf": [ { "type": "array", "items": { "type": "string" } }, { "type": "null" } ]
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
    }
  },
  "required": [
    "id",
    "provider",
    "label",
    "isDefault",
    "hasApiKey",
    "apiKeyHint",
    "baseUrl",
    "enabledModels",
    "updatedAt"
  ],
  "additionalProperties": false
}

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

This operation accepts an Idempotency-Key header: replaying the same request with the same key returns the original response instead of acting twice. See Idempotency.

Error codes — request.bad_request, llm.encryption_disabled, llm.invalid_base_url, llm.api_key_required, llm.connection_label_taken. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

PUT /api/v1/admin/llm/connections/{id} ​

Update an AI connection

Rename, replace the key, change the base URL or the allowed models. An absent apiKey keeps the one in place.

Access — Member session or API key with scope admin:write (administrator role required).

Parameters

NameInTypeRequired
idpathstringyes

Request body (application/json)

FieldTypeRequired
labelstringno
apiKeystringno
baseUrlstring (uri)no
withoutApiKeybooleanno
enabledModelsstring[]no
JSON Schema
json
{
  "type": "object",
  "properties": {
    "label": { "type": "string", "minLength": 1, "maxLength": 80 },
    "apiKey": { "type": "string", "minLength": 1, "maxLength": 512 },
    "baseUrl": { "type": "string", "maxLength": 2048, "format": "uri" },
    "withoutApiKey": { "type": "boolean" },
    "enabledModels": {
      "minItems": 1,
      "maxItems": 64,
      "type": "array",
      "items": { "type": "string", "minLength": 1, "maxLength": 128 }
    }
  }
}

Responses

200 — The connection state.

FieldTypeRequired
idstring (uuid)yes
provider"anthropic" | "openai" | "mistral" | "openrouter" | "ollama" | "openai_compatible"yes
labelstringyes
isDefaultbooleanyes
hasApiKeybooleanyes
apiKeyHintstring | nullyes
baseUrlstring | nullyes
enabledModelsstring[] | nullyes
updatedAtstring (date-time)yes

Same schema as POST /api/v1/admin/llm/connections.

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — request.bad_request, llm.connection_not_found, llm.connection_label_taken, llm.invalid_base_url, llm.encryption_disabled. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

DELETE /api/v1/admin/llm/connections/{id} ​

Remove an AI connection

Refused (409) while a published workflow references it, unless force=true; refused when it is the default and others exist. 204 even when it no longer exists.

Access — Member session or API key with scope admin:write (administrator role required).

Parameters

NameInTypeRequired
idpathstringyes
forcequerystringno

Responses

204 — Removed.

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — llm.connection_in_use, llm.connection_is_default. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

POST /api/v1/admin/llm/connections/{id}/default ​

Make an AI connection the default

The connection becomes the default of its provider; the previous default steps down. Recorded in the audit log.

Access — Member session or API key with scope admin:write (administrator role required).

Parameters

NameInTypeRequired
idpathstringyes

Responses

200 — The connection state.

FieldTypeRequired
idstring (uuid)yes
provider"anthropic" | "openai" | "mistral" | "openrouter" | "ollama" | "openai_compatible"yes
labelstringyes
isDefaultbooleanyes
hasApiKeybooleanyes
apiKeyHintstring | nullyes
baseUrlstring | nullyes
enabledModelsstring[] | nullyes
updatedAtstring (date-time)yes

Same schema as POST /api/v1/admin/llm/connections.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — llm.connection_not_found. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

GET /api/v1/admin/llm-providers ​

Describe every AI provider

The state of each provider (configured or not, catalogue, connections) and the resolved defaults, in one call.

Access — Member session or API key with scope admin:read (administrator role required).

Responses

200 — The providers.

FieldTypeRequired
providersobject[]yes
defaultsobjectyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "providers": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "anthropic",
              "openai",
              "mistral",
              "openrouter",
              "ollama",
              "openai_compatible"
            ]
          },
          "configured": { "type": "boolean" },
          "hasApiKey": { "type": "boolean" },
          "apiKeyHint": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "baseUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "preset": {
            "type": "object",
            "properties": {
              "provider": {
                "type": "string",
                "enum": [
                  "anthropic",
                  "openai",
                  "mistral",
                  "openrouter",
                  "ollama",
                  "openai_compatible"
                ]
              },
              "defaultBaseUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
              "baseUrlPolicy": { "type": "string", "enum": [ "fixed", "preset", "required" ] },
              "apiKey": { "type": "string", "enum": [ "required", "optional" ] },
              "jsonMode": {
                "type": "string",
                "enum": [ "tool", "json_schema", "json_object", "none" ]
              },
              "supportsModelDiscovery": { "type": "boolean" },
              "supportsTools": { "type": "boolean" },
              "supportsVision": { "type": "boolean" },
              "docsUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
            },
            "required": [
              "provider",
              "defaultBaseUrl",
              "baseUrlPolicy",
              "apiKey",
              "jsonMode",
              "supportsModelDiscovery",
              "supportsTools",
              "supportsVision",
              "docsUrl"
            ],
            "additionalProperties": false
          },
          "models": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "string" },
                "inputUsdPerMTok": { "anyOf": [ { "type": "number" }, { "type": "null" } ] },
                "outputUsdPerMTok": { "anyOf": [ { "type": "number" }, { "type": "null" } ] },
                "retentionCovered": { "type": "boolean" },
                "supportsTemperature": { "type": "boolean" },
                "enabled": { "type": "boolean" }
              },
              "required": [
                "id",
                "inputUsdPerMTok",
                "outputUsdPerMTok",
                "retentionCovered",
                "supportsTemperature",
                "enabled"
              ],
              "additionalProperties": false
            }
          },
          "enabledModels": {
            "anyOf": [
              { "type": "array", "items": { "type": "string" } },
              { "type": "null" }
            ]
          },
          "defaults": {
            "type": "object",
            "properties": {
              "classify": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
              "compose": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
              "extract": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
              "general": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
              "analyzer": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
            },
            "required": [ "classify", "compose", "extract", "general", "analyzer" ],
            "additionalProperties": false
          },
          "updatedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
              },
              { "type": "null" }
            ]
          },
          "connections": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                },
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "label": { "type": "string" },
                "isDefault": { "type": "boolean" },
                "hasApiKey": { "type": "boolean" },
                "apiKeyHint": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
                "baseUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
                "enabledModels": {
                  "anyOf": [
                    { "type": "array", "items": { "type": "string" } },
                    { "type": "null" }
                  ]
                },
                "updatedAt": {
                  "type": "string",
                  "format": "date-time",
                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
                }
              },
              "required": [
                "id",
                "provider",
                "label",
                "isDefault",
                "hasApiKey",
                "apiKeyHint",
                "baseUrl",
                "enabledModels",
                "updatedAt"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "provider",
          "configured",
          "hasApiKey",
          "apiKeyHint",
          "baseUrl",
          "preset",
          "models",
          "enabledModels",
          "defaults",
          "updatedAt",
          "connections"
        ],
        "additionalProperties": false
      }
    },
    "defaults": {
      "type": "object",
      "properties": {
        "classify": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        },
        "compose": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        },
        "extract": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        },
        "general": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        },
        "analyzer": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        },
        "assistant": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "anthropic",
                    "openai",
                    "mistral",
                    "openrouter",
                    "ollama",
                    "openai_compatible"
                  ]
                },
                "model": { "type": "string" }
              },
              "required": [ "provider", "model" ],
              "additionalProperties": false
            },
            { "type": "null" }
          ]
        }
      },
      "required": [ "classify", "compose", "extract", "general", "analyzer", "assistant" ],
      "additionalProperties": false
    }
  },
  "required": [ "providers", "defaults" ],
  "additionalProperties": false
}

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

GET /api/v1/admin/llm-providers/{provider} ​

Describe an AI provider

Returned even without a stored key (configured: false), catalogue and defaults included.

Access — Member session or API key with scope admin:read (administrator role required).

Parameters

NameInTypeRequired
providerpathstringyes

Responses

200 — The provider state.

FieldTypeRequired
provider"anthropic" | "openai" | "mistral" | "openrouter" | "ollama" | "openai_compatible"yes
configuredbooleanyes
hasApiKeybooleanyes
apiKeyHintstring | nullyes
baseUrlstring | nullyes
presetobjectyes
modelsobject[]yes
enabledModelsstring[] | nullyes
defaultsobjectyes
updatedAtstring (date-time) | nullyes
connectionsobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "provider": {
      "type": "string",
      "enum": [
        "anthropic",
        "openai",
        "mistral",
        "openrouter",
        "ollama",
        "openai_compatible"
      ]
    },
    "configured": { "type": "boolean" },
    "hasApiKey": { "type": "boolean" },
    "apiKeyHint": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
    "baseUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
    "preset": {
      "type": "object",
      "properties": {
        "provider": {
          "type": "string",
          "enum": [
            "anthropic",
            "openai",
            "mistral",
            "openrouter",
            "ollama",
            "openai_compatible"
          ]
        },
        "defaultBaseUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
        "baseUrlPolicy": { "type": "string", "enum": [ "fixed", "preset", "required" ] },
        "apiKey": { "type": "string", "enum": [ "required", "optional" ] },
        "jsonMode": {
          "type": "string",
          "enum": [ "tool", "json_schema", "json_object", "none" ]
        },
        "supportsModelDiscovery": { "type": "boolean" },
        "supportsTools": { "type": "boolean" },
        "supportsVision": { "type": "boolean" },
        "docsUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
      },
      "required": [
        "provider",
        "defaultBaseUrl",
        "baseUrlPolicy",
        "apiKey",
        "jsonMode",
        "supportsModelDiscovery",
        "supportsTools",
        "supportsVision",
        "docsUrl"
      ],
      "additionalProperties": false
    },
    "models": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "inputUsdPerMTok": { "anyOf": [ { "type": "number" }, { "type": "null" } ] },
          "outputUsdPerMTok": { "anyOf": [ { "type": "number" }, { "type": "null" } ] },
          "retentionCovered": { "type": "boolean" },
          "supportsTemperature": { "type": "boolean" },
          "enabled": { "type": "boolean" }
        },
        "required": [
          "id",
          "inputUsdPerMTok",
          "outputUsdPerMTok",
          "retentionCovered",
          "supportsTemperature",
          "enabled"
        ],
        "additionalProperties": false
      }
    },
    "enabledModels": {
      "anyOf": [ { "type": "array", "items": { "type": "string" } }, { "type": "null" } ]
    },
    "defaults": {
      "type": "object",
      "properties": {
        "classify": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
        "compose": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
        "extract": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
        "general": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
        "analyzer": { "anyOf": [ { "type": "string" }, { "type": "null" } ] }
      },
      "required": [ "classify", "compose", "extract", "general", "analyzer" ],
      "additionalProperties": false
    },
    "updatedAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        { "type": "null" }
      ]
    },
    "connections": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
          },
          "provider": {
            "type": "string",
            "enum": [
              "anthropic",
              "openai",
              "mistral",
              "openrouter",
              "ollama",
              "openai_compatible"
            ]
          },
          "label": { "type": "string" },
          "isDefault": { "type": "boolean" },
          "hasApiKey": { "type": "boolean" },
          "apiKeyHint": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "baseUrl": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "enabledModels": {
            "anyOf": [
              { "type": "array", "items": { "type": "string" } },
              { "type": "null" }
            ]
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "id",
          "provider",
          "label",
          "isDefault",
          "hasApiKey",
          "apiKeyHint",
          "baseUrl",
          "enabledModels",
          "updatedAt"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "provider",
    "configured",
    "hasApiKey",
    "apiKeyHint",
    "baseUrl",
    "preset",
    "models",
    "enabledModels",
    "defaults",
    "updatedAt",
    "connections"
  ],
  "additionalProperties": false
}

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — llm.unsupported_provider. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

PUT /api/v1/admin/llm-providers/{provider} ​

Configure an AI provider

Sets or replaces the default connection of the provider. The key is required on first configuration and optional afterwards (absent keeps it). Recorded in the audit log, never with the key.

Access — Member session or API key with scope admin:write (administrator role required).

Parameters

NameInTypeRequired
providerpathstringyes

Request body (application/json)

FieldTypeRequired
apiKeystringno
baseUrlstring (uri)no
withoutApiKeybooleanno
enabledModelsstring[]no
JSON Schema
json
{
  "type": "object",
  "properties": {
    "apiKey": { "type": "string", "minLength": 1, "maxLength": 512 },
    "baseUrl": { "type": "string", "maxLength": 2048, "format": "uri" },
    "withoutApiKey": { "type": "boolean" },
    "enabledModels": {
      "minItems": 1,
      "maxItems": 64,
      "type": "array",
      "items": { "type": "string", "minLength": 1, "maxLength": 128 }
    }
  }
}

Responses

200 — The provider state.

FieldTypeRequired
provider"anthropic" | "openai" | "mistral" | "openrouter" | "ollama" | "openai_compatible"yes
configuredbooleanyes
hasApiKeybooleanyes
apiKeyHintstring | nullyes
baseUrlstring | nullyes
presetobjectyes
modelsobject[]yes
enabledModelsstring[] | nullyes
defaultsobjectyes
updatedAtstring (date-time) | nullyes
connectionsobject[]yes

Same schema as GET /api/v1/admin/llm-providers/{provider}.

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — llm.unsupported_provider, request.bad_request, llm.encryption_disabled, llm.invalid_base_url, llm.api_key_required, llm.model_not_allowed. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

DELETE /api/v1/admin/llm-providers/{provider} ​

Remove an AI provider key

204 even when nothing was stored. The audit line says whether something was actually removed.

Access — Member session or API key with scope admin:write (administrator role required).

Parameters

NameInTypeRequired
providerpathstringyes

Responses

204 — Removed.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — llm.unsupported_provider. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

POST /api/v1/admin/llm-providers/{provider}/test ​

Test an AI provider

A real, minimal call. The verdict is in the body with a 200: a refused key is the result, not an error of this API. connectionId names the connection to probe; absent, the default one.

Access — Member session or API key with scope admin:write (administrator role required).

Parameters

NameInTypeRequired
providerpathstringyes
connectionIdquerystringno

Responses

200 — The verdict.

FieldTypeRequired
provider"anthropic" | "openai" | "mistral" | "openrouter" | "ollama" | "openai_compatible"yes
okbooleanyes
modelstringyes
latencyMsintegeryes
errorobjectno
JSON Schema
json
{
  "type": "object",
  "properties": {
    "provider": {
      "type": "string",
      "enum": [
        "anthropic",
        "openai",
        "mistral",
        "openrouter",
        "ollama",
        "openai_compatible"
      ]
    },
    "ok": { "type": "boolean" },
    "model": { "type": "string" },
    "latencyMs": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
    "error": {
      "type": "object",
      "properties": { "code": { "type": "string" }, "message": { "type": "string" } },
      "required": [ "code", "message" ],
      "additionalProperties": false
    }
  },
  "required": [ "provider", "ok", "model", "latencyMs" ],
  "additionalProperties": false
}

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — llm.unsupported_provider, llm.provider_not_configured, llm.connection_not_found, llm.connection_provider_mismatch. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

GET /api/v1/admin/llm/providers/{provider}/models ​

Discover the models a key can see

Asks the provider itself, with the instance key (ten-minute cache). 200 even on failure, like the test. HTTP errors remain for an unknown provider, one that does not list its models, no stored key, and a connection of another provider.

Access — Member session or API key with scope admin:read (administrator role required).

Parameters

NameInTypeRequired
providerpathstringyes
connectionIdquerystringno

Responses

200 — The discovered models.

FieldTypeRequired
provider"anthropic" | "openai" | "mistral" | "openrouter" | "ollama" | "openai_compatible"yes
okbooleanyes
modelsobject[]yes
cachedbooleanyes
fetchedAtstring (date-time) | nullyes
errorobjectno
JSON Schema
json
{
  "type": "object",
  "properties": {
    "provider": {
      "type": "string",
      "enum": [
        "anthropic",
        "openai",
        "mistral",
        "openrouter",
        "ollama",
        "openai_compatible"
      ]
    },
    "ok": { "type": "boolean" },
    "models": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ] },
          "contextLength": {
            "anyOf": [
              {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 9007199254740991
              },
              { "type": "null" }
            ]
          },
          "inputUsdPerMTok": { "anyOf": [ { "type": "number" }, { "type": "null" } ] },
          "outputUsdPerMTok": { "anyOf": [ { "type": "number" }, { "type": "null" } ] },
          "known": { "type": "boolean" }
        },
        "required": [
          "id",
          "label",
          "contextLength",
          "inputUsdPerMTok",
          "outputUsdPerMTok",
          "known"
        ],
        "additionalProperties": false
      }
    },
    "cached": { "type": "boolean" },
    "fetchedAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
        },
        { "type": "null" }
      ]
    },
    "error": {
      "type": "object",
      "properties": { "code": { "type": "string" }, "message": { "type": "string" } },
      "required": [ "code", "message" ],
      "additionalProperties": false
    }
  },
  "required": [ "provider", "ok", "models", "cached", "fetchedAt" ],
  "additionalProperties": false
}

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — llm.unsupported_provider, llm.discovery_not_supported, llm.provider_not_configured, llm.connection_provider_mismatch. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

GET /api/v1/admin/llm/defaults ​

Read the AI routing by purpose

One row per slot: the administrator’s choice, what the purpose will actually get, and whether the choice became inapplicable (key removed, model unticked).

Access — Member session or API key with scope admin:read (administrator role required).

Responses

200 — The routing.

FieldTypeRequired
entriesobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "entries": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "purpose": {
            "type": "string",
            "enum": [
              "default",
              "classify",
              "compose",
              "extract",
              "general",
              "analyzer",
              "assistant"
            ]
          },
          "override": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "purpose": {
                    "type": "string",
                    "enum": [
                      "default",
                      "classify",
                      "compose",
                      "extract",
                      "general",
                      "analyzer",
                      "assistant"
                    ]
                  },
                  "provider": {
                    "type": "string",
                    "enum": [
                      "anthropic",
                      "openai",
                      "mistral",
                      "openrouter",
                      "ollama",
                      "openai_compatible"
                    ]
                  },
                  "model": {
                    "anyOf": [
                      { "type": "string", "minLength": 1, "maxLength": 128 },
                      { "type": "null" }
                    ]
                  },
                  "temperature": {
                    "anyOf": [
                      { "type": "number", "minimum": 0, "maximum": 2 },
                      { "type": "null" }
                    ]
                  },
                  "maxOutputTokens": {
                    "anyOf": [
                      {
                        "type": "integer",
                        "exclusiveMinimum": 0,
                        "maximum": 200000
                      },
                      { "type": "null" }
                    ]
                  },
                  "updatedAt": {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
                  }
                },
                "required": [
                  "purpose",
                  "provider",
                  "model",
                  "temperature",
                  "maxOutputTokens",
                  "updatedAt"
                ],
                "additionalProperties": false
              },
              { "type": "null" }
            ]
          },
          "resolved": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "provider": {
                    "type": "string",
                    "enum": [
                      "anthropic",
                      "openai",
                      "mistral",
                      "openrouter",
                      "ollama",
                      "openai_compatible"
                    ]
                  },
                  "model": { "type": "string" }
                },
                "required": [ "provider", "model" ],
                "additionalProperties": false
              },
              { "type": "null" }
            ]
          },
          "overrideUnavailable": { "type": "boolean" }
        },
        "required": [ "purpose", "override", "resolved", "overrideUnavailable" ],
        "additionalProperties": false
      }
    }
  },
  "required": [ "entries" ],
  "additionalProperties": false
}

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

PUT /api/v1/admin/llm/defaults ​

Set the AI routing by purpose

Partial: absent slots do not move; provider: null clears a row. A choice on a provider without a key is accepted and reported as not yet applicable. Recorded in the audit log.

Access — Member session or API key with scope admin:write (administrator role required).

Request body (application/json)

FieldTypeRequired
defaultsobject[]yes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "defaults": {
      "minItems": 1,
      "maxItems": 7,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "purpose": {
            "type": "string",
            "enum": [
              "default",
              "classify",
              "compose",
              "extract",
              "general",
              "analyzer",
              "assistant"
            ]
          },
          "provider": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "anthropic",
                  "openai",
                  "mistral",
                  "openrouter",
                  "ollama",
                  "openai_compatible"
                ]
              },
              { "type": "null" }
            ]
          },
          "model": {
            "anyOf": [
              { "type": "string", "minLength": 1, "maxLength": 128 },
              { "type": "null" }
            ]
          },
          "temperature": {
            "anyOf": [
              { "type": "number", "minimum": 0, "maximum": 2 },
              { "type": "null" }
            ]
          },
          "maxOutputTokens": {
            "anyOf": [
              { "type": "integer", "exclusiveMinimum": 0, "maximum": 200000 },
              { "type": "null" }
            ]
          }
        },
        "required": [ "purpose", "provider", "model" ]
      }
    }
  },
  "required": [ "defaults" ]
}

Responses

200 — The routing, after write.

FieldTypeRequired
entriesobject[]yes

Same schema as GET /api/v1/admin/llm/defaults.

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — request.bad_request. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.

GET /api/v1/admin/llm-usage ​

Read the AI usage report

Aggregates per day, provider, model and context over [from, to). to defaults to now, from to thirty days earlier; the window cannot exceed a year. total.costUsd is null when no row had a price.

Access — Member session or API key with scope admin:read (administrator role required).

Parameters

NameInTypeRequired
fromquerystringno
toquerystringno

Responses

200 — The usage.

FieldTypeRequired
fromstring (date-time)yes
tostring (date-time)yes
rowsobject[]yes
totalobjectyes
JSON Schema
json
{
  "type": "object",
  "properties": {
    "from": {
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
    },
    "to": {
      "type": "string",
      "format": "date-time",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
    },
    "rows": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "day": { "type": "string" },
          "provider": {
            "type": "string",
            "enum": [
              "anthropic",
              "openai",
              "mistral",
              "openrouter",
              "ollama",
              "openai_compatible"
            ]
          },
          "model": { "type": "string" },
          "context": {
            "type": "string",
            "enum": [ "execution", "eval", "analyzer", "assistant" ]
          },
          "calls": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
          "inputTokens": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
          "outputTokens": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
          "costUsd": { "anyOf": [ { "type": "number" }, { "type": "null" } ] }
        },
        "required": [
          "day",
          "provider",
          "model",
          "context",
          "calls",
          "inputTokens",
          "outputTokens",
          "costUsd"
        ],
        "additionalProperties": false
      }
    },
    "total": {
      "type": "object",
      "properties": {
        "calls": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
        "inputTokens": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
        "outputTokens": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
        "costUsd": { "anyOf": [ { "type": "number" }, { "type": "null" } ] }
      },
      "required": [ "calls", "inputTokens", "outputTokens", "costUsd" ],
      "additionalProperties": false
    }
  },
  "required": [ "from", "to", "rows", "total" ],
  "additionalProperties": false
}

400 — The request does not match its schema.

401 — No valid session or API key (auth.unauthenticated).

403 — Refused: insufficient role (auth.forbidden), missing scope (api_key.scope_missing, details.required names it) or a route closed to API keys (api_key.session_required).

429 — The API key exceeded its rate limit (api_key.rate_limited); Retry-After says when to retry.

Error codes — request.bad_request. The common codes (request.bad_request, auth.unauthenticated, api_key.scope_missing, api_key.rate_limited…) apply to every route; see Errors.