Console
API keys
The ordinary way to reach the Gateway — a desktop client, a script, your own service. Org-owned, scoped, revocable, and metered like everything else.
An API key is how a caller reaches the Gateway: a client on your laptop, a CI job, a service you run. Keys belong to an organization, not to a machine and not to a person, so they survive both. An API key is the only kind of credential you can create.
Creating a key
In the Console, go to Operate → API Keys and press Create key. Give it a name, tick the scopes, optionally set an expiry of 30 days, 90 days or a year. The secret is shown exactly once. What the key can then reach is on Models, the surface directly under this one.
Anyone in the organization can see the list of keys, because nothing secret is stored to leak. Owners, admins and members can mint one; viewers cannot. A member can rename or revoke the keys they created, and an owner or admin can manage any key.
What a key looks like
Nine characters of marker, then eight hex characters of selector, then forty-eight hex characters of secret. The prefix (lsk_live_a1b2c3d4) is not secret. It is safe to show in a UI, print in a log line, or paste into a support conversation, and it is how a key is identified after it has been issued.
The selector is why verification is fast: one indexed read on the prefix, then a single constant-time hash comparison, rather than hashing every candidate row. The hash is plain SHA-256 rather than bcrypt or argon2 on purpose. A slow key-derivation function is the right answer for low-entropy human passwords; this is 192 bits from a CSPRNG, where there is nothing to brute-force and a slow hash would cost real latency on the inference path.
Scopes
A key can only do what it is scoped for, and the absence of a scope is a denial. New keys default to inference and usage:read. Asking for a scope that does not exist is rejected with the valid list rather than silently dropped.
| Scope | Allows |
|---|---|
| inference | Call the Gateway — chat completions and the model catalog |
| usage:read | Read this organization's requests, latency and cost |
| audit:read | Read the audit record (GET /api/v1/audit). Owners and admins only, and only while the key's creator still is one |
| agents:read | Vestigial. See Legacy, below |
| agents:write | Vestigial. See Legacy, below |
A key minted today should carry inference and usage:read and nothing else; a key for a SIEM collector, only audit:read (see Send the audit log to your SIEM). Scopes can be edited after the fact, along with the name. Doing so does not change the secret.
Keys belong to the organization
Every key is organization-level: managed inference, the organization's plan ceiling, model auto unless the call names one, and usage recorded against the org. On a paid plan, a call to a provider the organization added a key for in Settings › Provider keys runs on that key.
Using a key
X-Lobstack-Client is optional. It is recorded on the request trace and groups your traffic in the Console, truncated to 200 characters. It is a header, so it identifies nothing and authorizes nothing — it is a label. When it is absent the user agent is recorded instead.
What comes back
Every response carries the routing and cost decisions in headers, so you never have to infer what happened.
| Header | Meaning |
|---|---|
x-lobstack-request-id | The trace id. Quote it and the exact request can be looked up, including why it failed. |
x-lobstack-model | The model that actually served the request. |
x-lobstack-tier | The tier the request was scored into. |
x-lobstack-complexity | The complexity score that chose that tier. |
x-lobstack-routed | Whether Nex served a different model than the one requested. |
x-lobstack-cost-usd | What you owe for this request, priced when it ran. Empty means unpriced, which is not the same as free. |
x-lobstack-savings-usd | Dollars saved against the baseline model. Empty when there is no baseline. |
x-lobstack-baseline-model, -reason, -usd | What the saving was measured against and why: “named” if you asked for it, “plan_ceiling” if you sent “auto”. Sent only when a baseline exists. |
x-lobstack-quota-meter | Which allowance the quota headers describe: spend, requests or legacy. |
x-lobstack-savings-pct | The tier-level estimate. Not a measured dollar figure. |
x-lobstack-priced | Whether the served model was in the catalog at all. |
x-lobstack-metered | Whether the ledger write succeeded. Metering is best-effort. |
x-lobstack-mode | managed (Lobstack's provider key) or byok (yours). |
x-lobstack-principal | Which kind of credential authenticated: api_key, gateway_token or agent_secret. |
x-lobstack-dropped-params | Present only when a parameter was dropped, e.g. a temperature the model would 400 on. |
Errors
Failures carry a class alongside the status, so a client can branch on whose problem it is without matching on message text.
| Class | Whose problem | Retry? |
|---|---|---|
| auth | The credential was rejected. Fix the key. | No |
| quota | Allowance exhausted. Upgrade, top up, or wait for the reset. | After the reset |
| validation | The request was malformed. Fix the call. | No |
| provider | The upstream model provider failed. Not your fault. | Yes |
| timeout | We gave up waiting. | Yes |
| internal | Our bug. | Report it with the request id |
A rejected key is always a 401, whatever the reason. Internally the verification distinguishes malformed, unknown, revoked and expired, and the secret is compared before the lifecycle is checked so that a wrong secret cannot learn whether a given selector names a revoked key or no key at all.
Quota
API-key traffic is enforced against the organization's monthly allowance. Exhausting it returns 402 with a retry-after giving the seconds until the period resets.
Which counters arrive depends on the meter, so branch on x-lobstack-quota-meter first. A plan that includes dollars of model spend sends x-lobstack-quota-allowance-usd, -spent-usd and -remaining-usd and no request counters. The BYOK plan, no longer sold, and the older messages-per-month tiers send x-lobstack-quota-limit, -used and -remaining, plus -credits when you hold purchased credits; remaining includes them. x-lobstack-quota-resets is sent on any meter once the reset date is known. They are on every response, so a client can see the wall coming rather than hitting it. The three meters are set out on Pricing & plans.
Last used, rotation and revoking
The Console shows a Last used column so you can tell whether anything still depends on a key before you turn it off. It is throttled to one write a minute: the column exists to answer “is this still in use”, which a one-minute resolution answers just as well as a row update on every request, and a row update on every request would sit on the inference hot path. So treat the timestamp as accurate to the minute, not to the request.
Revoking is immediate and cannot be undone — issue a new key instead. Revoking twice is not an error, and does not move the timestamp that records when access actually ended. A revoked key stays listed in the Console under its own heading rather than disappearing, so its past requests remain attributable and the question asked right after a hurried revocation (“which key was that?”) still has an answer.
To rotate: mint the replacement, deploy it, confirm the old key's Last used has stopped moving, then revoke. Keys can also carry an expiry of up to ten years, and a credential with an end date is one fewer thing to remember to clean up.
Reading usage programmatically
A key with usage:read can query the same numbers the Console shows — identical arithmetic, so the two never disagree.
Group by day, model, provider, mode, key, client or app, over the month to date or 7, 14, 30 or 90 days. The response carries request and error counts, a failure breakdown by class, true p50/p95/p99 latency computed over raw values, and cost. A truncated flag tells you when a very large window means the totals are a floor rather than an exact figure. The same numbers are described on the Usage page.
Handling keys
Treat a key like a password. Keep it in an environment variable or a secret manager, never in source control, and never in client-side code a browser can read — a key in a web page is a key anyone who opens dev tools now has.
Issue one key per client rather than sharing one. That is what makes group_by=key answer “what caused this spike”, and what lets you revoke one thing without taking down everything else.
supabase/migrations/20260908_api_keys_and_request_traces.sql. On a deployment without it the surface says so rather than reporting an empty list.Legacy
Lobstack used to run agents in hosted VMs. That product was retired on 11 September 2026; these notes stay for anyone who used it.
Two credentials from that runtime still authenticate — a per-agent gateway token and the platform agent secret — but nothing issues new ones. They are reported in x-lobstack-principal as gateway_token and agent_secret. Quota is not enforced on them, only measured: enforcing it would start returning 402 to callers that have been over their allowance for weeks and working fine, so they get the advisory headers instead.
The agents:* scopes were written for a runtime that could be started, stopped and rebuilt. There is no machine left for them to control; they stay in the list so an existing key keeps its recorded grants rather than silently losing them.
Keys could once be bound to an agent row. POST /api/v1/keys now answers an agent_id with a 400. A key bound before then still inherits that row's model, mode and plan while the row exists.