Skip to main content

Documentation Index

Fetch the complete documentation index at: https://langwatch.ai/docs/llms.txt

Use this file to discover all available pages before exploring further.

The management REST API is the same surface the langwatch CLI and LangWatch dashboard use, exposed for scripts, CI pipelines, SDKs, and terraform providers. It runs on the LangWatch control plane (https://app.langwatch.ai), separately from the data-plane gateway (https://gateway.langwatch.ai). Authentication uses existing LangWatch API tokens (Authorization: Bearer lwp_... or X-Auth-Token: lwp_...). No new token format.
The CLI (langwatch virtual-keys ...) is built on this API. If you’re scripting from Node or Python, consider using the CLI rather than calling the REST directly — it handles pagination, JSON formatting, and error messages for you.

Base

Base URL: https://app.langwatch.ai/api/gateway/v1
Auth:     Authorization: Bearer <project_api_token>
Content:  application/json
Self-hosted: replace the base with your control plane’s DNS.

Virtual keys

List

GET /virtual-keys
Response:
{
  "data": [
    {
      "id": "vk_01HZX...",
      "name": "prod-key",
      "description": null,
      "environment": "live",
      "prefix": "lw_vk_live_01H",
      "last_four": "NZ0Z",
      "status": "ACTIVE",
      "principal_user_id": null,
      "project_id": "proj_01HZ...",
      "organization_id": "org_01HZ...",
      "provider_credential_ids": ["gpc_01HZ...", "gpc_01HZW..."],
      "config": { /* model_aliases, cache, fallback, ... */ },
      "created_at": "2026-04-10T12:00:00Z",
      "updated_at": "2026-04-18T18:21:00Z",
      "revoked_at": null,
      "last_used_at": "2026-04-18T21:03:12Z"
    }
  ]
}

Create

POST /virtual-keys
Body:
{
  "name": "ci-key",
  "description": "CI smoke tests",
  "environment": "live",                         // or "test"
  "principal_user_id": null,                     // optional
  "provider_credential_ids": ["gpc_01HZ..."],    // required, min 1
  "config": { /* optional partial config */ }
}
Response 201:
{
  "virtual_key": { /* full VK shape per List */ },
  "secret": "lw_vk_live_01HZX9K3MABCDEFGH...JIKLMN0Z"
}
The secret field is returned only this once. Persist it immediately.

Get

GET /virtual-keys/:id
Response: { "virtual_key": {...} }. No secret.

Update

PATCH /virtual-keys/:id
Body (all fields optional):
{
  "name": "new-name",
  "description": null,                         // null clears
  "provider_credential_ids": ["gpc_...", "..."],
  "config": { /* partial, merges with existing */ }
}

Rotate

POST /virtual-keys/:id/rotate
Response 200:
{
  "virtual_key": { /* ... */ },
  "secret": "lw_vk_live_NEW..."
}
The previous secret stops authenticating immediately (modulo ~60 s of gateway L1 cache TTL).

Revoke

POST /virtual-keys/:id/revoke
Idempotent: revoking an already-revoked VK returns 200 with the same status.

Budgets

List

GET /budgets
Response: { "data": [{...}, ...] }. Each entry has limit_usd and spent_usd as strings (decimal precision preserved).

Create

POST /budgets
Body:
{
  "scope": {
    "kind": "PROJECT",         // or ORGANIZATION, TEAM, VIRTUAL_KEY, PRINCIPAL
    "project_id": "proj_..."   // the id field depends on scope.kind
  },
  "name": "project-monthly-cap",
  "description": "Standard monthly envelope",
  "window": "MONTH",           // MINUTE | HOUR | DAY | WEEK | MONTH | TOTAL
  "limit_usd": 5000,           // or "5000.00"
  "on_breach": "BLOCK",        // or WARN
  "timezone": "Europe/Amsterdam"
}

Update

PATCH /budgets/:id
Updatable fields: name, description, limit_usd, on_breach, timezone.

Archive

DELETE /budgets/:id
Soft archive: preserves ledger history, stops enforcement on new requests.

Provider bindings

List

GET /providers
Response:
{
  "data": [
    {
      "id": "gpc_01HZ...",
      "model_provider_id": "mp_openai",
      "model_provider_name": "openai",
      "slot": "primary",
      "rate_limit_rpm": 10000,
      "rate_limit_tpm": 1000000,
      "rate_limit_rpd": null,
      "rotation_policy": "manual",
      "fallback_priority_global": 10,
      "health_status": "healthy",
      "disabled_at": null,
      "created_at": "2026-04-10T12:00:00Z"
    }
  ]
}

Create

POST /providers
Body:
{
  "model_provider_id": "mp_openai",   // the existing provider row id
  "slot": "primary",                  // free-text
  "rate_limit_rpm": 10000,
  "rate_limit_tpm": 1000000,
  "rate_limit_rpd": null,
  "rotation_policy": "manual",        // v1 accepts "manual" only; auto + external_secret_store are v1.1
  "extra_headers": null,
  "provider_config": null,
  "fallback_priority_global": 10
}
Response 201: { "provider_credential": { "id": "gpc_01HZ..." } }.

Update

PATCH /providers/:id

Disable

DELETE /providers/:id
Soft disable: existing VKs bound to this credential continue to resolve; new VK creation rejects this credential.

Cache rules

Organization-scoped overrides that modulate cache behaviour for requests routed through the gateway. Evaluated first-match-wins by priority DESC; a matched rule wins over the per-VK default but loses to a per-request X-LangWatch-Cache header. Full contract in Cache control.

List

GET /cache-rules
Requires gatewayCacheRules:view. Returns rules sorted priority DESC, excluding archived.
{
  "data": [
    {
      "id": "V1StGXR8_Z5jdHi6B-myT",
      "organization_id": "org_01HZ...",
      "name": "force-cache-enterprise",
      "description": "Force cache on Anthropic for enterprise-tagged VKs",
      "priority": 300,
      "enabled": true,
      "matchers": { "vk_tags": ["tier=enterprise"] },
      "action": { "mode": "force", "ttl": 600 },
      "mode_enum": "FORCE",
      "archived_at": null,
      "created_at": "2026-04-19T09:00:00Z",
      "updated_at": "2026-04-19T09:00:00Z"
    }
  ]
}
mode_enum is echoed in upper case alongside the lower-case action.mode so Prometheus / dashboards can filter by it without parsing the JSON action.

Get

GET /cache-rules/:id
Requires gatewayCacheRules:view. Returns 404 for archived rules — use the audit log to inspect removed rules.

Create

POST /cache-rules
Requires gatewayCacheRules:create. At least one matcher is required (rules that match every request must be declared explicitly — unsupported in v1). Matchers across non-null fields are ANDed.
{
  "name": "disable-cache-evals",
  "description": "Disable cache for evaluation traffic",
  "priority": 200,
  "enabled": true,
  "matchers": {
    "vk_prefix": "lw_vk_eval_",
    "request_metadata": { "x-langwatch-suite": "evals" }
  },
  "action": { "mode": "disable" }
}
Matcher fields (all optional, ANDed):
fieldtypesemantics
vk_idstringExact match against the VK’s display prefix form (lw_vk_live_...)
vk_prefixstringstrings.HasPrefix against the same VK display prefix
vk_tagsstring[]VK must carry EVERY listed tag (AND subset)
principal_idstringExact match against the VK’s principal user
modelstringExact match against the resolved model name (no regex; trailing * as a glob is accepted)
request_metadataobjectExact-match against request headers/metadata key-by-key
Action fields:
fieldtypesemantics
mode"respect" / "force" / "disable"Required. See Cache control for per-provider semantics
ttlint secondsOptional. Clamped to [0, 86400]. Only meaningful for force on providers that support TTL (Anthropic)
saltstring (max 64)Optional cache-bust tag — changing this on an existing rule forces regeneration on next hit
Response 201: { "cache_rule": { ...full row } }.

Update

PATCH /cache-rules/:id
Requires gatewayCacheRules:update. Partial update; matchers and action replace the stored value when provided (not merged field-by-field). Omitting them leaves the stored value untouched. Name, description, priority, enabled update independently.

Archive

DELETE /cache-rules/:id
Requires gatewayCacheRules:delete. Soft archive: sets archivedAt. The rule stops matching new requests. Returns the archived row (200, not 204) so scripts can confirm the archivedAt timestamp.

Errors

All responses follow the OpenAI-compatible envelope:
{
  "error": {
    "type": "bad_request",
    "code": "validation_error",
    "message": "provider_credential_ids must contain at least 1 element"
  }
}
See API: Errors for the full type enum. Common management-side types:
HTTPtypeMeaning
400bad_requestValidation / missing required field
401unauthenticatedMissing or invalid API token
403permission_deniedToken lacks the required RBAC scope
404not_foundResource doesn’t exist or isn’t visible in this project
409conflictName collision on create, stale update (optimistic concurrency)
422validation_errorSemantic error (e.g. window=MINUTE but limit_usd=100000, impossibly large per-minute cap)

Audit

Every write emits a row in the platform-wide AuditLog (gateway shape — targetKind / targetId / before / after). Visible under /settings/audit-log with the Source = “Gateway” badge; filter by Target (virtual_key / budget / provider_binding / cache_rule) to scope. Writes via API tokens are attributed to the API token’s resolved user. See Audit log for the full schema, REST export path, and migration note from the v3.0 gateway-only table.

Rate limits

Management endpoints are rate-limited to 100 req/min/token. For bulk operations, use the --format json CLI with xargs -P4 (the CLI sleeps 250 ms between retries on 429).

Shared service layer

Both the REST API and the tRPC routers (virtualKeys.*, gatewayBudgets.*, gatewayProviders.*) call the same service classes on the server — VirtualKeyService, GatewayBudgetService, GatewayProviderCredentialService. The only difference between REST and tRPC is the DTO shape (snake_case vs camelCase). Behaviour is identical. This matters for testing: the BDD scenarios in specs/ai-gateway/public-rest-api.feature include a parity check that asserts the two surfaces return equivalent data for the same resource after case normalization. If you rely on a field in one, it’s guaranteed to exist in the other.

OpenAPI

All REST routes are annotated with hono-openapi’s describeRoute schemas. The generated OpenAPI 3.1 spec is served at /api/gateway/v1/openapi.json and renders inside the platform’s API-reference UI. The CLI’s VirtualKeysApiService, GatewayBudgetsApiService, GatewayProvidersApiService DTOs are hand-authored against the same schemas; a v1.1 improvement is to regenerate them from the OpenAPI output so drift fails at build time.

See also

  • langwatch CLI — higher-level access.
  • RBAC — which scopes your token needs.
  • Security — how your API tokens and resulting writes are protected.