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. 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.

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
audit:readRead the audit record (GET /api/v1/audit). Owners and admins only, and only while the key's creator still is one
agents:readVestigial. See Legacy, below
agents:writeVestigial. 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

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 Nex 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. 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.

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

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.

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.

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.

Lobstack

An AI team that asks before it acts, and an API with a receipt on every call.

© LobstackXLinkedInGitHub