Management API reference
Every operation on the administration and management API.
Administrative control plane: keys, users, orgs, budgets, guardrails, credentials, compliance. Requires an attested administrator identity — see Authentication & Scoping.
Generated from the OpenAPI specification. A continuous-integration check enforces the specification against the running router in both directions — an undocumented route fails the build, and so does a documented route that no longer exists. This reference therefore cannot silently drift from the implementation.
122 operations across 21 groups.
A note on free-form responses
Some operations document their body as a free-form object (additionalProperties: true). That is deliberate, not an omission, and
it means one of two things:
- Provider passthrough. The gateway forwards the upstream provider’s payload verbatim. Restating the provider’s schema here would create a second source of truth that drifts from theirs. Use the provider’s own documentation for those shapes.
- Application-defined JSON. The value comes from a JSONB column the application does not structurally constrain.
Where a shape is pinned, this reference names the schema and lists its
fields. So ChatCompletionRequest — model*: string, … is a contract;
“free-form object” is an honest admission that it is not.
Contents
- key — 8 operations
- user — 5 operations
- team — 9 operations
- organization — 8 operations
- customer — 4 operations
- budget — 5 operations
- model — 5 operations
- credential — 6 operations
- guardrail — 7 operations
- callback — 3 operations
- config — 2 operations
- dataset — 4 operations
- developer — 9 operations
- eval_job — 4 operations
- eval_suite — 2 operations
- openapi — 1 operations
- pipeline — 12 operations
- schema — 1 operations
- tag — 4 operations
- tier — 1 operations
- v1 — 22 operations
key
POST /key/block
Soft-block a key (blocked keys 401 on /v1/* but remain queryable)
Request body (required): key: string, key_id: string
| Response | Meaning |
|---|---|
200 | Blocked. — blocked: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /key/delete
Delete one or more API keys
Request body (required): key: string, key_id: string, keys: array, key_aliases: array
| Response | Meaning |
|---|---|
200 | Deleted. — deleted_keys: array |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /key/generate
Generate a new API key
Request body (required): key_name: string, key_alias: string, user_id: string, team_id: string, max_budget: number|null, models: array, rpm_limit: integer|null, tpm_limit: integer|null … +3
| Response | Meaning |
|---|---|
201 | Key created; plaintext key returned only at this time. — key*: string, key_id: string, key_name: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /key/info
Look up a single API key by token or id
| Parameter | In | Required | Type |
|---|---|---|---|
key | query | no | string |
key_id | query | no | string |
| Response | Meaning |
|---|---|
200 | Key row. — APIKey — token*: string, id: string, key_name: string, key_alias: string |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /key/list
List API keys
| Parameter | In | Required | Type |
|---|---|---|---|
user_id | query | no | string |
team_id | query | no | string |
organization_id | query | no | string |
search | query | no | string |
page | query | no | integer |
page_size | query | no | integer |
| Response | Meaning |
|---|---|
200 | Paginated list of keys. — data: array, total: integer |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /key/unblock
Reverse a soft-block on a key
Request body (required): key: string, key_id: string
| Response | Meaning |
|---|---|
200 | Unblocked. — unblocked: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /key/update
Update an existing API key
Request body (required): key*: string, key_name: string, key_alias: string, max_budget: number|null, models: array, rpm_limit: integer|null, tpm_limit: integer|null, expires: string|null … +2
| Response | Meaning |
|---|---|
200 | Updated. — updated: boolean |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /key/{key}/regenerate
Regenerate (rotate) a key’s token in place
| Parameter | In | Required | Type |
|---|---|---|---|
key | path | yes | string |
| Response | Meaning |
|---|---|
200 | New plaintext key returned (rotation completed). — key*: string, key_id: string |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
user
POST /user/delete
Delete a user (soft-delete; spend history preserved)
Request body (required): user_id*: string
| Response | Meaning |
|---|---|
200 | Deleted. — deleted: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /user/info
Get a single user by id or email
| Parameter | In | Required | Type |
|---|---|---|---|
user_id | query | no | string |
user_email | query | no | string |
| Response | Meaning |
|---|---|
200 | User row. — User — user_id: string, user_email: string, user_role*: string, name: string, organization_id: string, team_id: string |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /user/list
List users
| Parameter | In | Required | Type |
|---|---|---|---|
page | query | no | integer |
page_size | query | no | integer |
user_role | query | no | string |
| Response | Meaning |
|---|---|
200 | Paginated user list. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /user/new
Create a new user
Request body (required): user_email: string, user_role: string, name*: string
| Response | Meaning |
|---|---|
201 | User created. — id: string, user_email: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /user/update
Update a user’s role or profile
Request body (required): user_id*: string, user_role: string|null
| Response | Meaning |
|---|---|
200 | Updated. — updated: boolean |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
team
POST /team/block
Soft-block a team
Request body (required): team_id*: string
| Response | Meaning |
|---|---|
200 | Blocked. — blocked: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /team/delete
Delete a team
Request body (required): team_id*: string
| Response | Meaning |
|---|---|
200 | Deleted. — deleted: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /team/info
Get a single team
41-13: response enriched additively (id, alias, name, org_id, org_name, member_count, max_budget, total_spend, current_spend, status, created_at) alongside team_id/team_alias/spend.
| Parameter | In | Required | Type |
|---|---|---|---|
team_id | query | yes | string |
| Response | Meaning |
|---|---|
200 | Team row. — object |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /team/list
List teams
41-13: rows enriched additively (see /team/info) alongside the pre-existing team_id/team_alias/spend/blocked.
| Parameter | In | Required | Type |
|---|---|---|---|
organization_id | query | no | string |
| Response | Meaning |
|---|---|
200 | Team list. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /team/member_add
Add a user to a team
Request body (required): team_id: string, user_id: string, role: string
| Response | Meaning |
|---|---|
200 | Added. — added: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /team/member_delete
Remove a user from a team
Request body (required): team_id: string, user_id: string
| Response | Meaning |
|---|---|
200 | Removed. — removed: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /team/new
Create a team
Request body (required): team_alias*: string, organization_id: string, max_budget: number|null, rpm_limit: integer|null, tpm_limit: integer|null, models: array
| Response | Meaning |
|---|---|
201 | Team created. — team_id: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /team/unblock
Reverse a soft-block on a team
Request body (required): team_id*: string
| Response | Meaning |
|---|---|
200 | Unblocked. — unblocked: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /team/update
Update a team
Request body (required): team_id*: string, team_alias: string, max_budget: number|null
| Response | Meaning |
|---|---|
200 | Updated. — updated: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
organization
POST /management/organizations/{id}/apply_tier
Apply a tier’s resolved limits to an organization (idempotent)
Resolves the tier (by external_id) from the plan_tiers catalog, merges any per-subscription override, and applies the effective limits to the organization’s budget/api-key rows idempotently. NULL-soft: an unsynced external_id applies the override only and never zeroes an existing budget. Control-plane principals only (tenant principal → 403). Org id comes from the path; any body id is inert.
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
Request body (required): external_id*: string, override: object
| Response | Meaning |
|---|---|
200 | Applied; returns the effective limits written to the org. — object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /organization/delete
Delete an organization
Request body (required): organization_id*: string
| Response | Meaning |
|---|---|
200 | Deleted. — deleted: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /organization/info
Get a single organization
41-13: response enriched additively (alias, name, metadata, member_count, team_count, total_spend, created_at) alongside id/organization_alias/spend.
| Parameter | In | Required | Type |
|---|---|---|---|
organization_id | query | yes | string |
| Response | Meaning |
|---|---|
200 | Organization row. — object |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /organization/list
List organizations
41-13: rows enriched additively (alias, name, member_count, team_count, total_spend, created_at) alongside id/organization_alias/spend.
| Response | Meaning |
|---|---|
200 | Organization list. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /organization/member_add
Add a user to an organization
Request body (required): organization_id: string, user_id: string, role: string
| Response | Meaning |
|---|---|
200 | Added. — added: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /organization/member_delete
Remove a user from an organization
Request body (required): organization_id: string, user_id: string
| Response | Meaning |
|---|---|
200 | Removed. — removed: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /organization/new
Create an organization
Request body (required): organization_alias*: string, max_budget: number|null, models: array
| Response | Meaning |
|---|---|
201 | Organization created. — organization_id: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /organization/update
Update an organization
41-13: this route now performs a REAL scoped UPDATE (it was a no-op that only audited intent). organization_alias + metadata are persisted via COALESCE (omitted field = column untouched), behind the unchanged own-org gateOrgWriteScope gate.
Request body (required): organization_id*: string, organization_alias: string, metadata: object, max_budget: number|null
| Response | Meaning |
|---|---|
200 | Updated. — updated: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
customer
POST /customer/delete
Delete a customer
Request body (required): customer_id*: string, id: string, end_user_id: string
| Response | Meaning |
|---|---|
200 | Deleted. — deleted: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /customer/list
List customers
41-15: rows enriched additively (external_user_id + alias — dashboard-parity duplicates of the spec-locked end_user_id / end_user_name, which keep their exact names and derivations — plus default_model, allowed_model_region) alongside the pre-existing id/spend/blocked/created_at.
| Response | Meaning |
|---|---|
200 | Customer list. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /customer/new
Create a customer
Request body (required): user_id*: string, alias: string, max_budget: number|null, models: array, end_user_id: string, end_user_name: string
| Response | Meaning |
|---|---|
201 | Customer created. — customer_id: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /customer/update
Update a customer
41-15: the handler targets by the additive id (row UUID) or by the
historical end_user_id (external user id) — both through the same
scope gate. end_user_name/blocked persist via COALESCE.
Request body (required): customer_id*: string, max_budget: number|null, id: string, end_user_id: string, end_user_name: string, blocked: boolean
| Response | Meaning |
|---|---|
200 | Updated. — updated: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
budget
POST /budget/delete
Delete a budget tier
Request body (required): budget_id*: string
| Response | Meaning |
|---|---|
200 | Deleted. — deleted: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /budget/info
Get a single budget tier
| Parameter | In | Required | Type |
|---|---|---|---|
budget_id | query | yes | string |
| Response | Meaning |
|---|---|
200 | Budget tier row. — object |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /budget/list
List budget tiers
41-14: rows enriched additively (name — blank rendered as “unnamed”, duration + budget_duration, soft_budget, rpm_limit, tpm_limit, reset_at, created_at, current_spend) alongside the pre-existing id/max_budget.
| Response | Meaning |
|---|---|
200 | Budget list. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /budget/new
Create a budget tier
41-14: request gains name + soft_budget (additive); budget_duration/rpm_limit/tpm_limit are now persisted (previously accepted but dropped).
Request body (required): budget_id*: string, max_budget: number, budget_duration: string, tpm_limit: integer|null, rpm_limit: integer|null, name: string, soft_budget: number|null
| Response | Meaning |
|---|---|
201 | Budget tier created. — budget_id: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /budget/update
Update a budget tier
41-14: this route now performs a REAL scoped UPDATE (it was a no-op that only audited intent) — COALESCE semantics, so an omitted field leaves the column untouched. Response shape unchanged.
Request body (required): budget_id*: string, max_budget: number, budget_duration: string, name: string, soft_budget: number|null, rpm_limit: integer|null, tpm_limit: integer|null
| Response | Meaning |
|---|---|
200 | Updated. — updated: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
model
POST /model/delete
Remove a model from the registry
Request body (required): model_id*: string
| Response | Meaning |
|---|---|
200 | Deleted. — deleted: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /model/info
Get a registered model
41-16: response enriched additively (id, name, created_at) alongside the pre-existing model_name/model_info/aosentry_params. Reads stay open to all admin tiers (global fleet catalog).
| Parameter | In | Required | Type |
|---|---|---|---|
model_id | query | no | string |
model_name | query | no | string |
| Response | Meaning |
|---|---|
200 | Model row. — object |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /model/list
List registered models
41-16: rows enriched additively (id, name, provider, health, response_time_ms, last_checked_at, lifecycle, is_self_hosted, created_at — dashboard-parity health/lifecycle join fields) alongside the pre-existing model_name/model_info. Reads stay open to all admin tiers and unfiltered (global fleet catalog); writes remain Instance-only (41-08).
| Response | Meaning |
|---|---|
200 | Model list. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /model/new
Add a model to the registry
Request body (required): model_name*: string, litellm_params: object, model_info: object
| Response | Meaning |
|---|---|
201 | Model registered. — model_id: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /model/update
Update a model registry entry
Request body (required): model_id*: string, model_name: string, litellm_params: object
| Response | Meaning |
|---|---|
200 | Updated. — updated: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
credential
POST /credential/delete
Delete a stored credential
41-16: gains additive id (row UUID) targeting, dual-keyed with the handler’s historical credential_name arm.
Request body (required): credential_id*: string, id: string, credential_name: string
| Response | Meaning |
|---|---|
200 | Deleted. — deleted: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /credential/list
List stored credentials (credential_info string values are redacted)
41-16: rows enriched additively (id, created_by, created_at, updated_at) alongside the pre-existing credential_name/credential_info; credential_info masking tightened from api_key-only to ALL string values (admin-family parity — deliberate security tightening, field names unchanged). Instance-only, like every /credential/* verb (41-08).
| Response | Meaning |
|---|---|
200 | Credential list. EVERY string value inside credential_info is — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /credential/new
Store a provider credential
Request body (required): credential_name: string, credential_info: object
| Response | Meaning |
|---|---|
201 | Credential stored. — credential_id: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /credential/provider-presets
List known provider defaults for the “Add Credential” form
Returns the provider preset catalog (name, api_base, custom_llm_provider,
api_key_header) with detected stamped per entry — whether a matching key
is present in the gateway’s environment. NEVER returns key material; only
the boolean. Instance-only, like every /credential/* verb.
| Response | Meaning |
|---|---|
200 | Provider presets. No key values are ever included. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /credential/provision-from-env
Create a provider credential from a key already in the gateway env
Reads the provider’s key from the gateway’s OWN environment, stores it as a credential and fires the autopopulate sync — the raw key never reaches the browser. DEV-ONLY: returns 404 when Environment=production, because production credentials come from the secret store. Instance-only.
Request body (required): provider*: string
| Response | Meaning |
|---|---|
201 | Credential stored from the environment key. — object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
404 | Unknown provider key, or the endpoint is disabled because the gateway |
5XX | Unexpected server-side failure. |
POST /credential/update
Update a stored credential
41-16: gains additive id (row UUID) targeting, dual-keyed with the handler’s historical credential_name arm — only the id arm can rename (credential_name becomes a write value); unknown id → 404. All /credential/* verbs are Instance-only (41-08).
Request body (required): credential_id*: string, id: string, credential_name: string, credential_values: string, credential_info: object
| Response | Meaning |
|---|---|
200 | Updated. — updated: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
guardrail
POST /guardrail/check
Evaluate a guardrail against an input payload (dry-run)
Request body (required): guardrail_id: string, input: string
| Response | Meaning |
|---|---|
200 | Evaluation result. — passed: boolean, details: object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /guardrail/delete
Delete a guardrail
Request body (required): guardrail_id*: string
| Response | Meaning |
|---|---|
200 | Deleted. — deleted: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /guardrail/list
List guardrails
41-14: rows enriched additively (name — duplicate of guardrail_name for parsers keyed on name, aosentry_params, guardrail_info) alongside the pre-existing id/guardrail_name/guardrail_type/enabled/created_at.
| Response | Meaning |
|---|---|
200 | Guardrail list. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /guardrail/logs
List guardrail evaluation logs
41-14: rows enriched additively (confidence, stage — execution_stage with “pre” default, original_content, modified_content, created_at) alongside the pre-existing id/request_id/guardrail_name/status/duration_ms; status stays the raw column.
| Parameter | In | Required | Type |
|---|---|---|---|
guardrail_id | query | no | string |
page | query | no | integer |
page_size | query | no | integer |
| Response | Meaning |
|---|---|
200 | Log entries. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /guardrail/logs/stats
Aggregated guardrail-log statistics
| Response | Meaning |
|---|---|
200 | Stats. — object |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /guardrail/new
Create a guardrail
41-14: an empty guardrail_type is inferred from the name / aosentry_params shape (admin-family inferGuardrailType parity); an operator-supplied type passes through unchanged.
Request body (required): guardrail_name: string, guardrail_type: string, mode*: string, config: object
| Response | Meaning |
|---|---|
201 | Guardrail created. — guardrail_id: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /guardrail/update
Update a guardrail
41-14 (docs catch-up): guardrail_name / guardrail_type / aosentry_params / enabled were already accepted and persisted by the handler; documented here because the dashboard’s enable/disable toggle converges onto {guardrail_id, enabled}. Writes are scope-gated (foreign-org / NULL-org targets → 403).
Request body (required): guardrail_id*: string, mode: string, config: object, guardrail_name: string, guardrail_type: string, aosentry_params: object, enabled: boolean
| Response | Meaning |
|---|---|
200 | Updated. — updated: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
callback
POST /callback/delete
Remove a registered callback
Request body (required): callback_id*: string
| Response | Meaning |
|---|---|
200 | Removed. — deleted: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /callback/list
List registered callbacks
| Response | Meaning |
|---|---|
200 | Callback list. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /callback/new
Register a webhook callback
Request body (required): url: string, events: array, secret: string
| Response | Meaning |
|---|---|
201 | Callback registered. — callback_id: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
config
GET /config/list
List runtime config entries
| Response | Meaning |
|---|---|
200 | Config map. — object |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /config/update
Update one or more runtime config entries
Request body (required): object
| Response | Meaning |
|---|---|
200 | Updated. — updated: boolean |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
dataset
GET /management/dataset/list
List datasets
Lists every dataset asset in the attested tenant’s namespace. Tenant isolation is structural — the namespace path IS the boundary (SC6).
| Response | Meaning |
|---|---|
200 | Dataset list (tenant-scoped). — data: array |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
POST /management/dataset/new
Create a dataset
Validates the content against the dataset JSON Schema (C-02; required name/storage_format/table_ref) and persists it as a catalog asset (asset_type=‘dataset’) in the attested tenant’s namespace. table_ref.snapshot_ids carries the Iceberg reproducibility pin (SC2).
Request body (required): name: string, content: object
| Response | Meaning |
|---|---|
201 | Dataset created. — asset_id: string, version: integer |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
POST /management/dataset/{id}/items
Append items to a dataset
Appends jsonl-style test-case items into the dataset asset’s provenance.items array and re-validates the merged content against the dataset schema before persisting via AIAssetStore.Update (which bumps the version + content-hash atomically).
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
Request body (required): items*: array
| Response | Meaning |
|---|---|
200 | Items appended. — appended: integer |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /management/dataset/{id}/sample_from_production
Sample production traffic into a dataset
Reads a bounded, tenant-filtered sample of production spend_logs rows (metadata.tenant_id = the attested tenant) and appends them as dataset items. sample_size is capped server-side; the tenant binding is the attested principal exclusively (no tenant_id wire field), so a cross-tenant sample is structurally impossible.
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
Request body (required): sample_size*: integer
| Response | Meaning |
|---|---|
200 | Sampled rows appended. — sampled: integer |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
developer
GET /developer/v1/audit
Audit entries involving the calling developer
Returns audit_logs rows where either the developer was the actor (changed_by = ‘api-key:’ — the attested actor string) OR the resource is one of the developer’s api_keys (table_name=‘api_keys’ AND object_id IN developer’s keys by aoid_credential_id). Newest first. Cross-developer leak is prevented by the IN subquery restricting object_id to keys owned by the calling developer.
| Parameter | In | Required | Type |
|---|---|---|---|
page | query | no | integer |
page_size | query | no | integer |
| Response | Meaning |
|---|---|
200 | Paginated audit entries. — data: array, page: integer, page_size: integer |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /developer/v1/keys
List the developer’s own active keys
Returns the calling developer’s active (non-discarded) API keys.
The raw token column is NEVER included — only the id and
public metadata. Pagination via standard limit/offset query
params.
| Parameter | In | Required | Type |
|---|---|---|---|
limit | query | no | integer |
offset | query | no | integer |
| Response | Meaning |
|---|---|
200 | List of keys (raw token NEVER included). — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /developer/v1/keys
Mint a self-service API key
Create a new API key bound to the calling developer’s attested credential (aoid_credential_id). Security invariants enforced server-side:
- aoid_credential_id is sourced from the attested context (request-body user_id / team_id / organization_id are dropped — never trusted).
- team_id and organization_id are zeroed.
- max_budget is clamped to (developer ceiling - allocated). Exceeding the remaining grant returns 400.
- models[] defaults to the developer’s own allowlist when omitted (NEVER empty for a restricted developer, because empty in api_keys = “all models” — bypass key blocked).
- models[] is intersected with the developer’s allowlist — a developer cannot grant a model outside their own grant set. Per-developer key cap (default 10, override via env DEVELOPER_MAX_KEYS_PER_USER) enforced before INSERT.
Audit row recorded with changed_by=“api-key:” via the attested actor string.
Request body (required): key_name: string, key_alias: string, max_budget: number|null, models: array, rpm_limit: integer|null, tpm_limit: integer|null, expires: string|null
| Response | Meaning |
|---|---|
201 | Key minted. Plaintext key returned only at this time. — id: string, key: string, key_alias: string |
400 | Validation error. Includes: |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
DELETE /developer/v1/keys/{id}
Revoke one of the developer’s keys (soft-delete)
Sets discarded_at = now() on the key. Same ownership filter as
GET: 404 (not 403) is returned for foreign or non-existent keys.
Audit row recorded with changed_by=“developer:
| Response | Meaning |
|---|---|
204 | Key revoked. |
401 | Missing or invalid bearer token. |
404 | Key not found OR not owned by the calling developer (same |
5XX | Unexpected server-side failure. |
GET /developer/v1/keys/{id}
Retrieve one of the developer’s keys
Returns the key’s public metadata. 404 is returned BOTH when
the key genuinely does not exist AND when it exists but is not
owned by the calling developer — distinguishing the two would
leak key existence (RESEARCH Pitfall: no enumeration via 403-vs-
404 split). The raw token is never included in the response.
| Response | Meaning |
|---|---|
200 | Key details (raw token NEVER included). — id: string, key_name: string, key_alias: string, max_budget: number |
401 | Missing or invalid bearer token. |
404 | Key not found OR not owned by the calling developer. The |
5XX | Unexpected server-side failure. |
GET /developer/v1/models/available
List models the developer can grant on minted keys
Returns the proxy_models registry INTERSECTED with the developer’s users.models[] allowlist. When the developer is unrestricted (users.models[] is empty), the full registry is returned. When restricted, only models in the developer’s grant set appear — out-of-allowlist models are SILENTLY ABSENT (no error, no enumeration leak).
This endpoint preserves the 404-on-unauthorized-model property (SC2) for the developer surface: a model NOT in the developer’s grant set is not discoverable through this list endpoint.
| Response | Meaning |
|---|---|
200 | Grantable model list. — object: string, data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /developer/v1/spend
Total spend + request count for the calling developer’s keys
Returns the calling developer’s total_spend + total_requests over the date window. Scoped by aoid_credential_id (the attested self-scope derived from the AOID-signed context). A developer NEVER sees another developer’s totals. Discarded keys’ historic spend IS included (the developer spent that money; the key was just revoked afterward).
| Parameter | In | Required | Type |
|---|---|---|---|
start_date | query | no | string |
end_date | query | no | string |
| Response | Meaning |
|---|---|
200 | Developer-scoped spend summary. — total_spend: number, total_requests: integer, start_date: string, end_date: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /developer/v1/spend/logs
Paginated spend_logs for the calling developer’s keys
Returns spend_logs rows for keys owned by the calling developer, ordered by start_time DESC (newest first). page is 0-indexed.
| Parameter | In | Required | Type |
|---|---|---|---|
page | query | no | integer |
page_size | query | no | integer |
| Response | Meaning |
|---|---|
200 | Paginated spend_logs (newest first). — data: array, page: integer, page_size: integer |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /developer/v1/usage
Per-key spend breakdown for the calling developer
Returns one row per api_key owned by the calling developer with request count + spend in the window. Sorted by spend DESC. Empty key aliases are preserved as-is — the portal handles display.
| Parameter | In | Required | Type |
|---|---|---|---|
start_date | query | no | string |
end_date | query | no | string |
| Response | Meaning |
|---|---|
200 | Per-key breakdown. — data: array |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
eval_job
GET /management/eval_job/list
List eval jobs (tenant-scoped)
Returns a paginated list of eval_jobs rows for the caller’s tenant, ordered newest-first (created_at DESC). Enforces the tenant isolation matrix from Obj 16 TRD 16-10 (mirrors ListRuns):
- Plain tenant admin: sees only own-tenant rows.
?tenant=
→ 403. - master_key class OR cross_tenant_admin entitlement: may pass
?tenant=
override (audited as eval_job.tenant_override).
aggregate_score and verdict are omitted when no eval_results exist yet for a job (no fake zeros). For per-job scores and verdict use GET /management/eval_job/{id}/results.
| Parameter | In | Required | Type |
|---|---|---|---|
limit | query | no | integer |
offset | query | no | integer |
status | query | no | string |
tenant | query | no | string |
| Response | Meaning |
|---|---|
200 | Eval job list page. — jobs: array, total: integer |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
POST /management/eval_job/new
Launch an eval pipeline run
Opens ONE pgx.Tx, INSERTs pipeline_runs + eval_jobs in the same tx, emits the pipeline.run.start provenance entry (A-04 outbox), and ExecuteWorkflow’s EvalRunWorkflow on TaskQueueEval with EVERY reproducibility input PINNED at run start (SC2): the suite version defaults to the catalog head when omitted, temperature is pinned to 0, and the reproducibility seed flows through unchanged. A Temporal failure rolls the whole tx back — no orphan run row. The tenant comes from the attested principal exclusively (SC6); there is no tenant_id wire field.
Request body (required): eval_suite_id*: string, eval_suite_version: integer, model_asset_id: string, model_version: string, judge_model_version: string, dataset_snapshot_ids: array, reproducibility_seed: integer
| Response | Meaning |
|---|---|
201 | Eval job created; Temporal run started. — eval_job_id: string, pipeline_run_id: string, status: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
GET /management/eval_job/{id}
Get one eval job
Reads eval_jobs + pipeline_runs from Postgres (never Temporal visibility — anti-pattern). The wrong-tenant gate (SC6) 404s a job owned by a different tenant — never leaks its existence.
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
| Response | Meaning |
|---|---|
200 | Eval job snapshot. — eval_job_id: string, tenant_id: string, eval_suite_id: string, eval_suite_version: integer, pipeline_run_id: string, status: string, pipeline_run_status: string, reproducibility_seed: integer |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /management/eval_job/{id}/results
Get eval-job scores + verdict
Returns the eval_scores rows + the latest eval_results verdict bound to THIS job (job_id guard). A missing verdict on a still-running job is NOT an error — the verdict field is simply absent. Same wrong-tenant gate (SC6) as GET /eval_job/{id}.
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
| Response | Meaning |
|---|---|
200 | Scores + verdict. — eval_job_id: string, scores: array, verdict: string, aggregate_score: number, result: object |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
eval_suite
POST /management/eval_suite/new
Create an eval suite
Validates the content against the eval_suite JSON Schema (C-02; required name/eval_type/test_cases) and persists it as a catalog asset (asset_type=‘eval_suite’) in the attested tenant’s namespace. Versioning + content-hash come from the catalog store. Returns the catalog asset_id and the just-assigned version.
Request body (required): name: string, content: object
| Response | Meaning |
|---|---|
201 | Eval suite created. — asset_id: string, version: integer |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
GET /management/eval_suite/{id}/versions
List eval suite versions
Surfaces the append-only version history for an eval_suite asset from the shipped AIAssetVersionStore. A job pins a specific suite version (SC2) by reading from here.
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
| Response | Meaning |
|---|---|
200 | Version history (newest-first). — data: array |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
openapi
GET /openapi/{filename}
Serve an embedded OpenAPI YAML file
Returns the bytes of one of the embedded OpenAPI YAML files (management.yaml, _components.yaml; v1.yaml after TRD 02). Public route — no authentication required. The contract has no secrets in it.
| Parameter | In | Required | Type |
|---|---|---|---|
filename | path | yes | string |
| Response | Meaning |
|---|---|
200 | The embedded YAML file. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
pipeline
GET /management/pipelines/definitions
List pipeline definitions
| Response | Meaning |
|---|---|
200 | Definition list. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /management/pipelines/definitions
Register a pipeline definition
Compiles the DSL via dsl.Compile, validates the content against
the pipeline_definition JSON Schema (C-02; 13-value operator
enum), and persists as a catalog asset under (aocore, pipelines).
Returns the catalog asset_id, the just-assigned version, and the
Temporal workflow type name (pipeline_<assetID-first-8>_v<version>).
Request body (required): name: string, dsl_source: string
| Response | Meaning |
|---|---|
201 | Definition registered. — asset_id: string, version: integer, workflow_type: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /management/pipelines/definitions/{id}
Get one pipeline definition
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
| Response | Meaning |
|---|---|
200 | Definition. — asset_id: string, name: string, current_version: integer, content_hash: string, content: object, created_at: string, updated_at: string |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /management/pipelines/hitl/pending
List pending human-in-the-loop reviews
Returns the pipeline_runs rows in status=‘awaiting_review’ scoped to
the caller’s tenant — the HITL reviewer inbox (G-07). Cross-tenant via
?tenant=other is master-key/admin-only and audit-logged; non-admin
callers receive 403 forbidden.
The reviewer-role filter is a documented C-04 RBAC stub: every authenticated caller currently sees ALL pending reviews for the (effective) tenant. step_id / step_name / reviewer_role project as empty strings until the parked-step descriptor lands with C-04.
| Parameter | In | Required | Type |
|---|---|---|---|
tenant | query | no | string |
| Response | Meaning |
|---|---|
200 | Pending HITL review list. — data: array |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
GET /management/pipelines/runs
List pipeline runs
Returns pipeline_runs rows filtered to the caller’s tenant (per
attested context). Cross-tenant via ?tenant=other is
master-key/admin-only and audit-logged; non-admin callers
receive 403 forbidden.
| Parameter | In | Required | Type |
|---|---|---|---|
tenant | query | no | string |
status | query | no | string |
definition_id | query | no | string |
limit | query | no | integer |
offset | query | no | integer |
| Response | Meaning |
|---|---|
200 | Run list. — data: array |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
POST /management/pipelines/runs
Start a pipeline run
Resolves the Temporal namespace + task queue from the attested principal via temporal.ResolveNamespace. Opens a pgx.Tx, INSERTs the pipeline_runs row, emits the pipeline.run.start audit chain entry (F-01 wire format) IN THE SAME tx, calls temporal.Client.ExecuteWorkflow, and commits. Wrong-tenant smuggling is structurally impossible — the request body has no tenant_id field.
Request body (required): definition_id*: string, definition_version: string, initial_inputs: object
| Response | Meaning |
|---|---|
202 | Run accepted. — run_id: string, temporal_workflow_id: string, temporal_namespace: string, status: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
503 | Service dependency unavailable — e.g. Temporal workflow engine not |
5XX | Unexpected server-side failure. |
GET /management/pipelines/runs/{id}
Get one pipeline run
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
| Response | Meaning |
|---|---|
200 | Run. — object |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /management/pipelines/runs/{id}/cancel
Cancel a pipeline run (graceful)
Sends Temporal CancelWorkflow. Activities observe workflow.IsCanceled and clean up; subsequent activities are not scheduled. Compare with /terminate which is destructive.
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
| Response | Meaning |
|---|---|
200 | Cancel accepted. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
503 | Service dependency unavailable — e.g. Temporal workflow engine not |
5XX | Unexpected server-side failure. |
GET /management/pipelines/runs/{id}/history
Paginated Temporal workflow history events
G-12 replaces the G-04 stub with real paginated Temporal history. Returns events in the Flutter wire shape consumed by G-05 EventHistoryView and G-06 projectStepsFromHistory. Pagination is offset-based: ?limit (default 100, max 1000) + ?next_page_token (opaque stringified offset). Final page omits next_page_token. When Temporal is not configured, returns 503 temporal_not_configured.
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
limit | query | no | integer |
next_page_token | query | no | string |
| Response | Meaning |
|---|---|
200 | Paginated Temporal workflow history events. — data: array, next_page_token: string |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
503 | Service dependency unavailable — e.g. Temporal workflow engine not |
5XX | Unexpected server-side failure. |
POST /management/pipelines/runs/{id}/signal
Send a signal to a pipeline run
Forwards a Temporal signal (e.g. human_review_decision with
payload {approved: bool, reason: string} for the human_review
operator).
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
Request body (required): signal_name*: string, payload: object
| Response | Meaning |
|---|---|
200 | Signal delivered. |
400 | Malformed body or invalid parameters. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
503 | Service dependency unavailable — e.g. Temporal workflow engine not |
5XX | Unexpected server-side failure. |
GET /management/pipelines/runs/{id}/stream
SSE live updates for a pipeline run
Server-Sent Events stream. Polls Temporal every 2 seconds and
emits one of three event types: state-change (status
transition), heartbeat (no change), terminal (final
status; stream closes). Latency budget: 2.3s p99 from state
change to client render (G-RESEARCH Domain 5).
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
| Response | Meaning |
|---|---|
200 | SSE stream (text/event-stream). |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /management/pipelines/runs/{id}/terminate
Terminate a pipeline run (destructive)
Sends Temporal TerminateWorkflow. Activities are NOT given a graceful shutdown signal; the workflow halts immediately. Use /cancel for graceful shutdown.
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
Request body (optional): reason: string
| Response | Meaning |
|---|---|
200 | Terminate accepted. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
503 | Service dependency unavailable — e.g. Temporal workflow engine not |
5XX | Unexpected server-side failure. |
schema
GET /management/schema
Return the management API JSON schema descriptor
Self-describing endpoint that returns a machine-readable summary of the management API surface. Independent of the OpenAPI spec served at /openapi/management.yaml — see go/internal/api/management/schema.go.
| Response | Meaning |
|---|---|
200 | Schema descriptor. — object |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
tag
POST /tag/delete
Delete a tag
Request body (required): tag_id*: string, id: string, name: string
| Response | Meaning |
|---|---|
200 | Deleted. — deleted: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /tag/list
List tags
41-15: rows enriched additively (id — the UI row identity and update/delete mutation key) alongside the pre-existing name/color/description.
| Response | Meaning |
|---|---|
200 | Tag list. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /tag/new
Create a tag
Request body (required): tag_name*: string, description: string, name: string, color: string
| Response | Meaning |
|---|---|
201 | Tag created. — tag_id: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /tag/update
Update a tag
41-15: promoted from a recorded no-op to a REAL scoped UPDATE. The
handler targets by the additive id (row UUID — required for rename,
which cannot address the row it renames by name) or by the unique
name; name/color/description persist via COALESCE (omit to leave
unchanged). Both arms ride the same scope gate.
Request body (required): tag_id*: string, tag_name: string, description: string, id: string, name: string, color: string
| Response | Meaning |
|---|---|
200 | Updated. — updated: boolean |
401 | Missing or invalid bearer token. |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
tier
POST /management/tiers
Upsert a plan-tier catalog row (control-plane sync target)
Idempotent upsert of a tier into the global plan_tiers catalog, matched on external_id (= eden-biz plan_key). First push inserts (201); re-push updates in place (200). Control-plane principals only; a tenant principal is rejected (403).
Request body (required): external_id*: string, slug: string, max_budget: number|null, soft_budget: number|null, rpm_limit: integer|null, tpm_limit: integer|null, models: array
| Response | Meaning |
|---|---|
200 | Tier updated in place (existing external_id). — object |
201 | Tier created (new external_id). — object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
v1
POST /management/v1/agent/chat
Attested chat completion (agent LLM step)
OpenAI-compatible chat+tools completion authenticated by the ATTESTED Principal (no API key). Mounted under AttestedAdminMiddleware. Accepts the OpenAI chat shape (messages + tools) and reuses the same LLM proxy as /v1/chat/completions: resolves model→provider→credential, runs guardrails, calls the upstream provider, and returns the assistant message + tool_calls + usage. Used by the agent loop worker so the LLM step runs as the attested operator identity rather than a service API key. Streaming is not supported on this path.
Request body (required): model: string, messages: array, tools: array, tool_choice: any
| Response | Meaning |
|---|---|
200 | Chat completion response (OpenAI shape). — id: string, object: string, model: string, choices: array, usage: object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /management/v1/billing-export/usage
Versioned cursor-paginated billing-data export (v1 pull seam)
Stable versioned pull API serving daily per-org usage/spend aggregates to EXTERNAL billing systems. AOCore produces billing-usable data; external systems price and invoice (locked decision 3).
CONTRACT STABILITY RULES (see docs/billing/billing-export-seam.md):
- version field is always “v1”; v2 would use /billing-export/v2/usage.
- Records for a closed UTC day are immutable (aggregate over spend_logs).
- Field evolution is additive-only: new fields may appear; existing fields are never removed or renamed without a path-level version bump.
- Cursor is opaque (base64 of internal position); callers must NOT parse or construct cursor values.
- Complete-or-loud: any tenant schema error returns 500 naming the tenant (billing data must be complete or explicitly broken — never silently partial, unlike orgs/usage).
- v1 is PULL (stateless, replayable, external biller controls cadence). Push (scheduled exporter/webhook) is the designed additive extension (see seam doc); it is NOT built in v1.
Ordering: (period_start ASC, org ASC) — deterministic and stable. Auth: AttestedAdminMiddleware.
| Parameter | In | Required | Type |
|---|---|---|---|
cursor | query | no | string |
limit | query | no | integer |
| Response | Meaning |
|---|---|
200 | Page of daily per-org usage records. — version: string, records: array, next_cursor: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
500 | Unexpected server-side failure. |
5XX | Unexpected server-side failure. |
DELETE /management/v1/catalog/tags
Clear a catalog tag
Request body (required): ns: string, key: string, target*: object
| Response | Meaning |
|---|---|
200 | Tag cleared. |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /management/v1/catalog/tags
List catalog tags
| Response | Meaning |
|---|---|
200 | Tag list. — data: array |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
POST /management/v1/catalog/tags
Apply a catalog tag
Request body (required): ns: string, key: string, value: string, target: object
| Response | Meaning |
|---|---|
200 | Tag applied. |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /management/v1/catalog/vector-tables
List vector tables
Paginated, ACL-filtered list of vector tables.
| Parameter | In | Required | Type |
|---|---|---|---|
cursor | query | no | string |
limit | query | no | integer |
search | query | no | string |
| Response | Meaning |
|---|---|
200 | Vector table list page. — items: array, next_cursor: string |
401 | Missing or invalid bearer token. |
5XX | Unexpected server-side failure. |
GET /management/v1/catalog/vector-tables/{id}
Get vector table detail
Full vector table detail including schema + vector columns. ACL-enforced.
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
| Response | Meaning |
|---|---|
200 | Vector table detail. — object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /management/v1/catalog/vector-tables/{id}/index-status/{jobId}
Poll vector index rebuild status
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
jobId | path | yes | string |
| Response | Meaning |
|---|---|
200 | Index job status. — status: string, progress_pct: number |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /management/v1/catalog/vector-tables/{id}/rebuild-index/{column}
Rebuild a vector column index
Triggers an async IVF_HNSW_SQ index rebuild for the column. Returns a job id.
| Parameter | In | Required | Type |
|---|---|---|---|
id | path | yes | string |
column | path | yes | string |
| Response | Meaning |
|---|---|
202 | Rebuild accepted. — job_id: string |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /management/v1/groups/
List all groups
| Response | Meaning |
|---|---|
200 | The list of groups. — data: array |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
POST /management/v1/groups/
Create a group
Creates a new group (catalog_principal_roles row) plus a dedicated companion catalog role so per-group permissions can be granted. A name collision returns 409.
Request body (required): name*: string, catalog: string
| Response | Meaning |
|---|---|
201 | Group created. — group: object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
409 | A group with this name already exists. |
5XX | Unexpected server-side failure. |
DELETE /management/v1/groups/{id}
Delete a group
| Response | Meaning |
|---|---|
200 | Group deleted. — object |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /management/v1/groups/{id}
Get a group by id
| Response | Meaning |
|---|---|
200 | The group. — group: object |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /management/v1/groups/{id}/members
List a group’s members
| Response | Meaning |
|---|---|
200 | The member principal (actor) strings. — data: array |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
POST /management/v1/groups/{id}/members
Add a member to a group
Request body (required): principal_id*: string
| Response | Meaning |
|---|---|
200 | Member added (idempotent). — object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
POST /management/v1/groups/{id}/members/remove
Remove a member from a group
Request body (required): principal_id*: string
| Response | Meaning |
|---|---|
200 | Member removed (idempotent). — object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
POST /management/v1/groups/{id}/permissions
Grant a permission to a group
Grants an AssetPrivilege to the group (applied to the group’s catalog role). object_type/object_id/tenant default to ai_asset / “” / “”.
Request body (required): privilege*: string, object_type: string, object_id: string, tenant: string
| Response | Meaning |
|---|---|
200 | Permission granted (idempotent). — object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
POST /management/v1/groups/{id}/permissions/remove
Revoke a permission from a group
Request body (required): privilege*: string, object_type: string, object_id: string, tenant: string
| Response | Meaning |
|---|---|
200 | Permission revoked (idempotent). — object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
404 | Resource not found. |
5XX | Unexpected server-side failure. |
GET /management/v1/orgs/usage
Per-org usage aggregates (quota dashboard data source)
Returns per-org (tenant-slug) request/token/spend aggregates over a rolling time window. Fans out over all tenant schemas + public via ForEachTenantSchema. Continue-on-error: one broken tenant schema does NOT 500 the response — instead “partial”: true is set and the broken schema is skipped. This is the data source for the TRD 7-08 Flutter quota dashboard.
Wire contract (frozen — 7-08 codes against this verbatim):
- window accepts 7d|30d|90d, default 30d.
- orgs sorted ascending by slug.
- org="" rows (public/legacy scope) included only when non-zero AND tenant mode is active.
- partial=true signals at least one tenant schema was unavailable.
Auth: AttestedAdminMiddleware (attested admin identity required). No billing semantics — raw usage + recorded spend_usd from spend_logs.
| Parameter | In | Required | Type |
|---|---|---|---|
window | query | no | string |
| Response | Meaning |
|---|---|
200 | Per-org usage aggregates for the window. — window: object, orgs: array, partial*: boolean |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
GET /management/v1/tenants
List provisioned tenant slugs
Returns the sorted list of tenant slugs this instance is provisioned for (cfg.TenantSlugs / AOCORE_TENANT_SLUGS, Obj 18 TRD 18-04 contract). When no slugs are configured (multi-tenancy dormant) returns an empty array with dormant=true. Internal multi-org framing only — no reseller or downstream-tenant concepts (7-CONTEXT.md locked decisions 1-2). Consumed by the admin shell org switcher (7-10). Supersedes the G-07 NamespaceSwitcher hardcoded stub as the real tenant-discovery source.
| Response | Meaning |
|---|---|
200 | Tenant slug list. — tenants: array, dormant: boolean |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |
POST /management/v1/trino/query
Execute a READ-ONLY Trino query
Runs a SELECT-class statement against Trino and returns columns + rows.
READ-ONLY enforced server-side (LOCKED decision 5): any non-SELECT
statement (INSERT/UPDATE/DELETE/MERGE/CREATE/DROP/ALTER/TRUNCATE/GRANT/
SET/CALL, multi-statement, or EXPLAIN ANALYZE of a write) is rejected
with 403 and error.code trino_read_only BEFORE reaching Trino —
defense in depth above the UI gate. Writes belong in pipelines where
they carry provenance. Per-identity rate-limited; results capped (413
with Retry-With-Pagination on overflow). Returns 503 when Trino is not
configured.
Request body (required): sql*: string, args: array
| Response | Meaning |
|---|---|
200 | Query results. — columns: array, rows: array, stats*: object |
400 | Malformed body or invalid parameters. |
401 | Missing or invalid bearer token. |
403 | Read-only violation (error.code trino_read_only) — a non-SELECT |
413 | Result exceeds the row cap; retry with pagination. |
429 | Per-identity rate limit exceeded. |
503 | Service dependency unavailable — e.g. Temporal workflow engine not |
5XX | Unexpected server-side failure. |
GET /management/v1/trino/query/history
List the caller’s read-only query history
Returns the calling identity’s own Trino workbench query history (Objective 7 TRD 7-01), newest first, capped at 100 rows. Scope is implicit and unforgeable: the actor is taken from the resolved attested principal, never from a query parameter, so no cross-actor access path exists. Reads Postgres (not Trino), so it functions even when the Trino coordinator is unconfigured.
| Response | Meaning |
|---|---|
200 | The caller’s query history (newest first, max 100). — queries*: array |
401 | Missing or invalid bearer token. |
403 | Authenticated but not authorized — e.g. a non-admin caller |
5XX | Unexpected server-side failure. |