Authentication & keys
Every request to the gateway is authenticated with a gateway API key
(gw_live_…).
Where the key goes
Section titled “Where the key goes”| Surface | Header |
|---|---|
Provider proxy (/openai/…, /anthropic/…, …) | The provider’s native auth header — Authorization: Bearer gw_live_… for OpenAI-style APIs, x-api-key: gw_live_… for Anthropic |
Self-service API (/gw/…) | Authorization: Bearer gw_live_… |
The gateway replaces your key with the real provider credential server-side; your key never reaches the provider.
Scopes
Section titled “Scopes”Each key carries a set of scopes:
| Scope | Allows |
|---|---|
inference:use | Calling providers through the proxy |
stats:read | GET /gw/stats, GET /gw/usage, GET /gw/async, GET /gw/evidence, Stripe billing reads (GET /gw/stripe/config, GET /gw/stripe/events) |
keys:manage | GET /gw/ceiling, GET/POST /gw/keys, DELETE /gw/keys/{id}, sub-accounts (/gw/subaccounts), budgets (/gw/budgets), branding (/gw/branding), Stripe billing writes (PUT /gw/stripe/config, mappings, customers, event retry) |
A request without the required scope gets 403.
Model entitlements
Section titled “Model entitlements”Besides scopes, each key has entitlements — glob rules that control which models it may call. Each rule lives on one of three axes:
axis: "provider"(default) —(provider, model_pattern)over the inference provider and its native model id, as before.axis: "vendor"—(vendor, model_name)over the catalog slugauthor/model— thevendorglob matches the author-part (e.g.alibaba+qwen3-*): the rule follows the model through the catalog regardless of which provider hosts it.axis: "kind"—(kind, model_pattern)over the catalog modality (llm | image | video | 3d | embedding | rerank | audio) and the model name glob. Only matches catalog-known slugs, same asvendor. Adenyrule on this axis only narrows access; anallowrule can only be granted to a child key if the issuing key’s own ceiling already carries that exactkindrule (kind-allow grants are operator-issued, not self-service).
Policy is default-deny and deny-wins across all axes: a model is allowed only
if some allow rule matches and no deny rule matches. Calling a
non-entitled model returns 403 before the request ever reaches the provider.
Author and kind rules apply to models found in the catalog — a model the
catalog does not know cannot be granted (or matched) by an author or kind rule.
Check what your key can do:
curl https://api.sociaro.com/gw/me -H "Authorization: Bearer gw_live_..."# {"scopes":["inference:use"],"entitlements":[{"axis":"provider","provider":"openai","model_pattern":"gpt-4o*","effect":"allow"}]}Issuing keys for your team
Section titled “Issuing keys for your team”With a keys:manage key you can issue child keys scoped down for each
service or teammate — see POST /gw/keys.
Rules:
- A child key’s scopes and entitlements must fit inside your organization’s
ceiling (
GET /gw/ceiling); anything beyond it is rejected with403and no key is created. - The plaintext key is returned exactly once — store it immediately.
- Keys are immutable: to change permissions, revoke and issue a new one.
- Each organization has a cap on active keys (default 1000; your
operator can raise it per client). Hitting the cap rejects
POST /gw/keyswith403; revoking a key frees a slot immediately. - Revocation (
DELETE /gw/keys/{id}) is a soft delete — usage history is preserved. Revocation takes effect immediately on the gateway instance that handled the request; a revoked key may keep working for up to ~30 seconds on other gateway instances (auth cache TTL), or up to ~90 seconds if the database is briefly unavailable right as that cache entry expires (the cache serves the last-valid entry through a short grace window during an outage).
Listing keys and last-used tracking
Section titled “Listing keys and last-used tracking”GET /gw/keys (scope keys:manage) lists your organization’s keys without
their secrets. Each entry includes a last_used_at timestamp:
curl https://api.sociaro.com/gw/keys -H "Authorization: Bearer gw_live_..."# [# {"id":"<uuid>","key_prefix":"gw_live_ab12","status":"active",# "scopes":["inference:use"],"sub_account_id":null,# "created_at":"2026-05-01T10:00:00Z","last_used_at":"2026-06-13T08:41:12Z"}# ]How last_used_at is maintained:
- Every authenticated request stamps the key’s
last_used_atasynchronously and best-effort — the write runs in a background goroutine and never blocks or fails your request. - It is throttled to roughly one write per key per 60 seconds, so a key hammered with traffic is not stamped on every call. Treat the value as approximate to within that window, not a precise last-request clock.
- It is
nulluntil the key is used for the first time. - Under a multi-instance deployment each instance throttles independently, so the stamp may lag by up to the throttle window.
last_used_at is surfaced only by GET /gw/keys. GET /gw/me describes the
current key and returns only {scopes, entitlements} — it does not include
last_used_at. Use it to spot dormant keys you can safely revoke.
Key safety
Section titled “Key safety”- Treat
gw_live_…like any production secret: environment variables or a secret manager, never source control. - Issue one key per service/environment so revocation is surgical.
- Use the least scopes possible — a server that only does inference needs
nothing beyond
inference:use.