Skip to content

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. Two older credentials still authenticate — a per-agent gateway token and the platform agent secret, both minted before the agent-VM runtime was retired — but nothing issues new ones, and an API key is the only kind 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 bind it to a legacy agent row, 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.

Any member of the organization can see the list of keys, because nothing secret is stored to leak. Only an owner or an admin can mint one, because a key is spend authority.

The secret is shown onceWhat is stored is a SHA-256 hash of the whole key, plus a short non-secret prefix used to look it up. Nobody — including Lobstack — can recover the value afterwards. If you lose it, revoke the key and issue another.

What a key looks like

text
lsk_live_a1b2c3d4e5f6...
└──┬───┘ └──┬───┘└───┬───┘
   │        │        └─ secret, 24 random bytes, never stored
   │        └────────── selector, 4 random bytes, stored as the lookup prefix
   └─────────────────── environment marker: live in production, test elsewhere

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.

ScopeAllows
inferenceCall the Gateway — chat completions and the model catalog
usage:readRead this organization's requests, latency and cost
agents:readRead agent state. Vestigial — the routes it guarded went with the agent-VM runtime
agents:writeChange agent state. Vestigial for the same reason

Two of the four are worth being blunt about. The agents:* pair was written for a runtime that could be started, stopped and rebuilt, and that runtime has been retired — there is no machine left for them to control. They remain in the scope list so an existing key keeps its recorded grants rather than silently losing them, and a key minted today should carry inference and usage:read and nothing else.

Scopes can be edited after the fact, along with the name. Doing so does not change the secret.


Binding a key to an agent row

A key can optionally be bound to one agent_instances row. A bound key inherits that row — its configured model, its managed-or-BYOK mode, its plan tier and its billing attribution. The row must belong to the same organization, and that is checked at creation, so a binding cannot be used to reach another org's billing.

What the row is not, any more, is a machine. The agent-VM runtime was retired and agent_instances survives as the billing record that Gateway authentication reads on every call; columns like server_tier and region are still on it and no longer describe anything. Binding is therefore a way of inheriting an older account's configuration, not a way of driving something.

An unbound key is organization-level: managed inference, the organization's plan ceiling, model auto, and usage recorded against the org with no agent attached. The Console shows these as Organization in the Bound to column, and every key minted today is this kind.

Losing the row does not revoke the keyIf the bound row goes, the binding is cleared and the key keeps working as an organization-level credential rather than silently dying with it. Its past requests stay attributable, because the trace and ledger rows keep their own foreign keys.

Using a key

Any OpenAI-compatible client worksbash
curl https://www.lobstack.ai/api/gateway/v1/chat/completions \
  -H "Authorization: Bearer $LOBSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Lobstack-Client: my-app/1.0" \
  -d '{
    "model": "auto",
    "messages": [{ "role": "user", "content": "Summarize today'"'"'s tickets." }]
  }'

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.

HeaderMeaning
x-lobstack-request-idThe trace id. Quote it and the exact request can be looked up, including why it failed.
x-lobstack-modelThe model that actually served the request.
x-lobstack-tierThe tier the request was scored into.
x-lobstack-complexityThe complexity score that chose that tier.
x-lobstack-routedWhether Token Intelligence served a different model than the one requested.
x-lobstack-cost-usdWhat you owe for this request, priced when it ran. Empty means unpriced, which is not the same as free.
x-lobstack-savings-usdDollars saved against the baseline model. Empty when there is no baseline.
x-lobstack-baseline-model, -reason, -usdWhat 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-meterWhich allowance the quota headers describe: spend, requests or legacy.
x-lobstack-savings-pctThe tier-level estimate. Not a measured dollar figure.
x-lobstack-pricedWhether the served model was in the catalog at all.
x-lobstack-meteredWhether the ledger write succeeded. Metering is best-effort.
x-lobstack-modemanaged (Lobstack's provider key) or byok (yours).
x-lobstack-principalWhich kind of credential authenticated: api_key, gateway_token or agent_secret.
x-lobstack-dropped-paramsPresent only when a parameter was dropped, e.g. a temperature the model would 400 on.
Quote the request idIt is on successful responses, on error responses, and in the error body. It is the fastest route from “something went wrong” to the row that says what.

Errors

Failures carry a class alongside the status, so a client can branch on whose problem it is without matching on message text.

json
{
  "error": {
    "message": "monthly allowance exhausted ($10.00 of $10.00 of model spend). Add a top-up or upgrade the plan, or wait for the period to reset.",
    "type": "quota",
    "code": 402,
    "request_id": "9f1c8f42-3a7e-4c19-9a0b-1d2e3f4a5b6c"
  }
}
ClassWhose problemRetry?
authThe credential was rejected. Fix the key.No
quotaAllowance exhausted. Upgrade, top up, or wait for the reset.After the reset
validationThe request was malformed. Fix the call.No
providerThe upstream model provider failed. Not your fault.Yes
timeoutWe gave up waiting.Yes
internalOur 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. BYOK 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.

Agent tokens are not enforced, only measuredEnforcement applies to API keys and not to agent credentials. That asymmetry is deliberate: switching them to enforced would start returning 402 to callers that have been over their allowance for weeks and working fine. They get the advisory headers so the data to make that decision honestly is being collected in the meantime.

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.

bash
curl "https://www.lobstack.ai/api/v1/usage?range=7d&group_by=model" \
  -H "Authorization: Bearer $LOBSTACK_API_KEY"

Group by day, model, key or agent, over 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 surface is described on the API monitor 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.

Requires a migrationAPI keys arrive with supabase/migrations/20260908_api_keys_and_request_traces.sql. On a deployment without it the surface says so rather than reporting an empty list.
Lobstack

One key over every frontier model, a receipt on every call, and an agent that waits before it changes anything.

© 2026 LobstackAll rights reserved  Status