A virtual key (VK) is a LangWatch-issued credential that a downstream client (SDK, coding CLI, production app) presents to the gateway in place of a provider-native API key. The gateway resolves the VK to an owning scope, a set of provider credentials, a fallback chain, a cache policy, guardrails, blocked patterns, and budgets — everything the VK is allowed to do and how it is observed. VKs replace raw provider keys in your applications. The provider keys themselves stay inside LangWatch (configured under Settings → Model Providers, the same surface you already use for evaluators and playground) and are never shared with downstream clients.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.
Quickstart — first request in 5 min
Create a VK, call
/v1/chat/completions, read correlation headers.Use your VK from Python / TypeScript
OpenAI / Anthropic SDK drop-in — just swap
base_url and api_key.Plug your VK into Claude Code / Codex / Cursor
Route coding-assistant traffic through the gateway for budget + audit.
Manage VKs from the terminal
langwatch virtual-keys create | list | rotate | revoke — scriptable.Format
lw_vk_{live|test}_<26-char-ULID> — 40 characters total.
lw_vk_— fixed prefix, grep-friendly, scans well in DLP / GitGuardian.liveortest— environment prefix. Prevents accidentally using a test key against production or vice versa.- 26-char Crockford-base32 ULID — time-prefixed, monotonic, renders sensibly in the dashboard and in your logs.
lw_vk_live_01HZX9) are visible in the list.
Server-side, the secret is stored as hex(hmac_sha256(server_pepper, secret)). Peppered HMAC-SHA256 is the right primitive for API keys (as opposed to passwords) — the VK body already carries 130 bits of entropy as a ULID, so argon2id-style stretching would just add 50–100 ms of pointless latency on every cold resolve-key call. A short prefix is stored alongside for grep/lookup.
Creating a VK
Under AI Gateway → Virtual Keys click New virtual key and fill:| Field | Meaning |
|---|---|
| Name | Human label. Used in the UI and in langwatch.vk.name trace attribute. |
| Environment | live (default) or test. Reflected in the key prefix. |
| Primary provider credentials | The credentials the gateway uses first. Dropdown of Model Provider entries on this project. |
| Fallback chain | Ordered list of additional provider credentials. Any failures on the primary (5xx / timeout / 429 / network) attempt the next in order. |
| Models allowed | Optional allowlist. Blank = “any model the provider credentials support.” |
| Model aliases | Map of client-friendly names → provider/model. E.g. gpt-4o: azure/my-deployment. |
| Cache policy | respect (default), force, or disable. |
| Guardrails | Attach existing LangWatch evaluators as pre / post / stream_chunk hooks — the drawer has a dedicated picker section listing every project evaluator with executionMode = AS_GUARDRAIL. Fail-open toggles on pre and post directions surface the concrete 403 / 50 ms / terminal-SSE enforcement shape so operators know what the toggle actually does. See Guardrails → Attaching guardrails to a VK. |
| Blocked patterns | Regex allow/deny for tool names, MCP servers, URLs. |
| Initial budget | Optionally attach a budget on creation. Budgets can always be added later. |
VK detail page
Clicking a row in the list opens the detail page (/gateway/virtual-keys/<vk_id>). Four sections, top-to-bottom:
- Header — name + action bar: Audit history (deep-links into
/settings/audit-log?targetKind=virtual_key&targetId=<vk_id>with a pre-filled filter chip showing only this VK’s events alongside any related platform actions), Edit, Rotate, Revoke. The Audit history deep-link is the fastest way to reconstruct “who changed this VK, when” during an incident. - Identity / Activity — id, prefix, environment, status, description, and humanised Last used + Created (hover for exact timestamp). Revision number shown when non-zero — increments on every
gateway.virtual_key.updatedaudit row. - Provider fallback chain — ordered rows, each showing provider icon + readable name + slot badge (
primary/fallback-1/fallback-2/ …) + credential id. Clarifies at-a-glance which provider is in the driver’s seat and what falls back when it errors. - Configuration summary — compact read-only view (no drawer needed) of the runtime config that’s actually in effect:
- Tags — colored subtle badges, the same strings shown on the list row.
- Cache mode — badge (
respect/force/disable) + TTL on force. - Rate limits —
rpm+rpdoutline badges (or em-dash if unset). - Model aliases — expanded as
alias → targetpairs, up to 5 visible with “+N more” overflow. - Blocked patterns — grouped by dimension (
tools/mcp/urls/models) with red-tinted regex chips; click-through opens the edit drawer at that section. - Guardrails — count + direction breakdown (
N pre / M post / K stream_chunk), each clickable to the guardrail config.
- Usage (last 30 days) — stat tiles (Total spend / Requests / Avg $/request), a 30-day filled area sparkline, Spend-by-model row, and the top 10 recent debits table (When relative / Model / Tokens in→out / Amount smart-decimals / Latency ms). Guarded on
virtualKeys:view+gatewayUsage:view.
Edit) or dedicated flows (Rotate / Revoke).
Rotation
Rotate a VK when you believe the secret may have leaked or as part of routine hygiene.- Open the VK in the list.
- Click Rotate secret.
- LangWatch mints a new secret and displays it exactly once.
- The old secret remains valid for a 24-hour grace window to let in-flight deploys update.
- After the grace window ends, only the new secret works.
vk_id, same ownership, same configuration — only the secret changes. Observability traces remain continuous across rotation.
Revocation
Revocation immediately invalidates the secret. There is no grace period on revoke. Gateway caches are invalidated within 60 seconds via the/internal/gateway/changes long-poll. If you need instant invalidation everywhere (e.g. incident response), restart the gateway pods — the next request that hits them will pull fresh state before serving.
Revoked VKs remain in the database (soft-delete) for audit. Their traces still appear in analytics.
Scoping
A VK belongs to exactly one project. The project’s team and organisation are implicit owners. Traffic attribution on every trace:langwatch.virtual_key_idlangwatch.project_idlangwatch.team_idlangwatch.organization_idlangwatch.principal_id— the user or service account that owns the VK (for principal-scoped budgets)
Personal access vs shared
Two flavours:- Personal access VK — bound to one user principal. Best for “give the engineer a key their local tools can use.” Inherits budgets scoped to that user.
- Shared VK — bound to a service-account principal. Best for applications. Can be rotated independently of user offboarding.
lw_vk_live_… format; the difference is the principal the VK resolves to and which budgets apply.
Permissions
Acting on VKs requires the following permissions (2-segmentresource:action):
| Action | Permission |
|---|---|
| View list | virtualKeys:view |
| Create | virtualKeys:create |
| Update config | virtualKeys:update |
| Rotate | virtualKeys:rotate |
| Delete / revoke | virtualKeys:delete |
| All of the above | virtualKeys:manage |
How the gateway resolves your VK
The first time the gateway sees a presented VK:- Calls
/internal/gateway/resolve-keyon the LangWatch control-plane → receives a signed JWT (15-minute TTL) with identity claims (vk_id,project_id,principal_id, etc.) plus arevisionnumber. - Calls
/internal/gateway/config/:vk_id(withIf-None-Match: <revision>on subsequent fetches) → receives the fat config (providers, fallback, guardrails, budgets). - Caches both in-memory (L1 LRU) and optionally in Redis (L2, shared across gateway pods).
- Subscribes to
/internal/gateway/changes?since=<revision>long-poll — on any mutation to any VK, the gateway invalidates and re-fetches.
- First request on a cold cache: 2-4 ms of control-plane overhead.
- Warm request after first: tens of microseconds of overhead.
- Control-plane offline: gateway continues serving from warm cache until JWT TTL expires (15 min default); with bootstrap mode on, it can serve indefinitely.