Skip to main content

Every request that reaches Rolter must carry a virtual key — a gateway-scoped credential that controls which models a client can access and counts against any configured budgets or rate limits. It is not the key your provider issued, and not the admin token: see Which key do I need? if you are not sure which one you are holding. Rolter accepts the same virtual keys in two header styles so it stays compatible with any OpenAI- or Anthropic-SDK-based client out of the box.

Header styles#

Rolter recognises two authentication headers. Use whichever your SDK or client already produces — both accept the same virtual keys.

OpenAI style

Authorization: Bearer <virtual-key>

Used with /v1/chat/completions, /v1/completions, and /v1/models.

Anthropic style

x-api-key: <virtual-key>

Used with /v1/messages.

Open mode (local development)#

When no [[virtual_keys]] entries are present in your configuration, Rolter runs in open mode and forwards every request without checking credentials. This is convenient when developing locally against a private gateway instance.

rolter.toml
Warning

Never run in open mode on a publicly reachable gateway. Any caller would be able to consume your upstream provider quota without restriction.

Configuring virtual keys#

Add one [[virtual_keys]] block per key to your rolter.toml. Each block supports the following properties:

keystringrequired#

The raw key value clients present in the Authorization or x-api-key header.

namestring#

A human-readable label for the key — shown in logs and the dashboard.

modelsarray of strings#

An allow-list of model names this key may request. An empty array (the default) grants access to every route.

disabledboolean#

Set to true to revoke the key immediately without removing it from your config. The key will be rejected as if it did not exist.

Default: "false"

expires_atstring (RFC 3339)#

An optional expiry instant in RFC 3339 format. Rolter rejects the key at or after this time. Leave unset for a non-expiring key.

rolter.toml
Note

In the dashboard the same allow-list is a list of the project’s routes to tick off, so a model name never has to be typed from memory. A model the project does not route yet can still be added by hand, and an entry already on a key that no route serves is kept rather than dropped — the stored value is the same array of names either way.

Warning

sk-rolter-dev is published in the repository, the README and these docs, and it allows every model. A gateway that serves other hosts must not carry it. When it is an active virtual key and the gateway listens on anything but a loopback address, the gateway logs a warning at startup and again on every config reload that still has it, and rolter check reports it as a failure. Delete its [[virtual_keys]] entry and mint a key of your own.

Note

The raw key value is visible only at the moment you add it to your configuration or create it via the API. Store it in a secrets manager (such as a CI secret or a vault) immediately — it cannot be recovered later.

Session affinity#

For multi-turn conversations where you need the same upstream replica to handle every turn, attach a session identifier to your request. Rolter uses this header with the consistent_hash and pipeline load-balancing strategies to pin the conversation to one backend.

Tip

Use a stable, opaque identifier like a user-and-thread composite key. Rolter hashes the value to select a target — it is never forwarded to the upstream provider.

Deployment-wide ingress policy#

Settings → Security carries two rules that every gateway in the fleet picks up on its next config poll — no restart, no redeploy.

Note

There is no Enforce virtual keys on inference switch on this screen any more. A gateway that holds virtual keys always requires one, outside the auth bypass routes below. A gateway that holds none decides from how it was started: one managed by a control plane (started with --snapshot-url) refuses every request with 401, and one running from its own config file stays open for local development. Only a require_auth line in that gateway’s own config file changes this, in either direction — require_auth = false is the one way to open a managed gateway that holds no keys, and it belongs to whoever runs the process rather than to the dashboard.

The switch only ever reached managed gateways, which were already closed, so it changed nothing and was removed. Saving from an older dashboard that still sends the field is accepted; the field is ignored. A config file that still sets [security] virtual_key_required = true is read as [server] require_auth = true, with a warning to move it.

Required headers

Name/value pairs every request must carry — a service-mesh identity header, a WAF marker, a tenant tag. A request missing one, or carrying a different value, is refused with 403 required_header_missing before routing and before authentication.

The refusal names the header but never the expected value, so a shared secret can safely be one. /healthz and /readyz are exempt: a probe is your own deployment’s traffic, and filtering it takes the gateway down rather than protecting it.

A saved rule that the control plane cannot read, which only a row written around the API with SQL can cause, is never read as “no rule”. The control plane stops publishing config instead, so every gateway keeps enforcing the rule it has, a gateway that restarts in the meantime serves nothing until it is repaired, and parts of the dashboard that show the effective config return an error. GET /api/v1/config/problems names the setting and the entry at fault, and the Effective config screen lists that line under its error. Saving the rule again on this screen, or with PUT /api/v1/security-settings, repairs it. See Upgrading.

Auth bypass routes

Exact /v1/... paths served without a key — typically /v1/models for a sidecar that reads the catalogue. Matching is exact: listing /v1/models does not open /v1/models/gpt-4o, and wildcards are rejected when you save.

Nothing is attributed to a key on a bypassed path, because no key was presented. Budgets and per-key rate limits do not apply there.

Editing the lists#

Allowed origins, allowed headers, required headers and auth bypass routes are edited one entry per line. A comma is not a separator, so a required header value can contain commas (x-ids: 1,2,3).

The dashboard checks every line against the rules the control plane applies when you save. An entry that fails stays in the field with its line number named under it, Save stays off until every entry passes, and nothing is sent in the meantime. An entry is never dropped to make a save go through.

ListEach line must be
Allowed originsan exact http:// or https:// origin, with no wildcard, path, query, fragment or user info
Allowed headersa header name: letters, digits and the punctuation RFC 9110 allows in a token, no spaces
Required headersname: value, split at the first colon, with a valid header name and a value that is not empty
Auth bypass routesan exact path under /v1/, with no wildcard, placeholder, query, fragment or spaces

One rule is the dashboard’s own. The control plane stores required headers as a map, so a header named on two lines would keep only one of the two values. The dashboard refuses the second line instead of choosing for you, and compares names without regard to case, as the gateway does.

Saving a change that loosens security#

A save that adds an Auth bypass route, or removes a Required header, asks first. The dialog lists exactly what was added or removed, says what each one means, and nothing is sent until you confirm. Cancelling keeps your edits in the form. A save that only tightens, such as a route removed or a header required, goes out at once, and so does adding an Allowed origin.

Save is enabled only while something differs from what was loaded. The cards you edited are marked Edited, and Discard changes puts every field back.

After you save#

The footer states when the save landed and which gateways run it. The control plane compares the config version each gateway last reported with the current one, the same converged flag the Cluster screen shows, and the footer counts the live gateways that match. A gateway applies a save on its next config poll, every 5 seconds by default and at once when Redis is configured. A gateway that has never identified itself to the control plane cannot be counted, and the footer says that instead of claiming it picked the change up.

Error responses#

Rolter returns standard HTTP error codes for authentication and authorisation failures.

401 — Missing or invalid key

Returned when the request carries no auth header, or when the presented key does not match any active virtual key. Check that the Authorization: Bearer or x-api-key header is present and contains the correct value.

403 — Model not allowed

Returned when the virtual key’s models allow-list does not include the model you requested. Either add the model to the key’s allow-list or use a key that grants access to that model.

402 — Budget exceeded

Returned when a spend budget at any scope (org, team, project, or key) has been exhausted for the current period. See Budgets & Rate Limits to adjust or reset limits.

429 — Rate limit hit

Returned when the request or token rate for the key’s scope chain has exceeded its per-minute cap. The response includes a Retry-After header indicating how many seconds to wait before retrying.

Signing in to the dashboard#

POST /api/v1/auth/login refuses with a stable code the dashboard renders directly, so a failed sign-in says what went wrong instead of appearing to succeed:

CodeStatusWhat the sign-in screen shows
invalid_credentials401that email and password do not match an account
password_login_disabled403the organization requires single sign-on
too_many_attempts429how long the lock lasts, read from Retry-After

The email-only gate — where the dashboard opens with no session at all — is reserved for an open-mode control plane running with no store, where the auth endpoints are not served. Anywhere else, a failed sign-in leaves you on the sign-in screen with the reason on it.

Staying signed in#

A session token is valid for seven days. The dashboard re-checks the one it stored every time it loads, with GET /api/v1/auth/me, and takes the answer literally:

  • 200 — the session is live. The account’s superadmin flag and its memberships come from that answer, so the role shown in the sidebar and the organization the scope switcher opens on are the server’s view, not a stale copy from the last sign-in.
  • 401 — the session is over. The stored token is discarded and the sign-in screen appears with “Your session expired — sign in again.” Any other request that is refused the same way ends the session too, so an expired token cannot linger behind a screen that merely fails to load.
  • anything else — a network failure, a 5xx, or a control plane that does not serve /api/v1/auth/* at all. The session is kept: a control plane that blinked is not a reason to sign you out.

What ends a session early#

A session ends before its seven days are up when:

  • you sign out (POST /api/v1/auth/logout)
  • a superadmin deactivates or deletes the account, or the identity provider deprovisions it over SCIM
  • the account’s second factor is reset
  • the account changes its own password (POST /api/v1/auth/password): every other session ends and the one it was changed from stays. See Change your password
  • a superadmin sets a new password for the account (PUT /api/v1/users/{id} with a password). The usual reason for a reset is that the old password is in someone else’s hands, so every session opened with it ends on the spot. The one exception is the session that made the request: a superadmin who resets their own password stays signed in on that page and is signed out everywhere else. The user.update audit entry records how many sessions the reset ended (sessions_revoked)