API reference¶
Every endpoint of Keera Gateway, generated from its OpenAPI spec. To get started, see The API. Errors are listed in Errors.
One listener serves three APIs, told apart by path:
/apiis the inference API. It speaks the OpenAI and Anthropic formats, so their SDKs work with only a new base URL and an API key./controlis the control API, which the panel and thekeeracommand use./sandboxattaches to a sandbox.
Errors share one shape: {"error": {"message", "type", "code"}}.
Inference¶
OpenAI- and Anthropic-shaped requests, behind an API key.
Create a chat completion¶
POST /api/v1/chat/completions
OpenAI's Chat Completions API, for any chat model or router the key may use, whatever provider serves it.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
X-Request-Id |
header | string | An id for the request. Kept if it is up to 64 of A-Z a-z 0-9 . _ : -, otherwise replaced. |
X-Client-Request-Id |
header | string | The client's own id, as OpenAI's API takes it: printable ASCII, up to 512 characters. It is logged and sent on to OpenAI models. An invalid one is ignored. |
X-Keera-Client |
header | string | The client's own name, which wins over the User-Agent in reports. |
Content-Encoding |
header | string | The body may be gzip or deflate. One of identity, gzip, x-gzip, deflate. |
X-Keera-Session |
header | string | Names the session (task) this request belongs to, instead of the one the gateway derives from the opening prompt. X-Session-Id, Helicone-Session-Id and X-Claude-Code-Session-Id are read too; the first with a value wins. See docs/sessions.md. |
Request body
| Field | Type | Description |
|---|---|---|
model |
string | Required. A model or router alias from GET /api/v1/models. |
messages |
array of object | Required. |
stream |
boolean | Answer as server-sent events (text/event-stream). Default false. |
max_tokens |
integer | |
max_completion_tokens |
integer | |
tools |
array of object |
Responses
| Status | Description |
|---|---|
| 200 | The provider's answer, in this API's format. Content: application/json, text/event-stream. |
| 400 | The body or a field is not valid. Codes include invalid_body, missing_model, context_length_exceeded and unsupported_parameter. |
| 401 | No valid credential was sent. |
| 402 | A budget is spent (budget_exceeded), or the organisation's prepaid credit for the deployment's provider keys is (credit_exhausted). Not 429: it does not lift by waiting. |
| 403 | Refused by a filter (filter_refused), a model the organisation may not use until it has paid (org_limited), or a subscription key while the deployment takes none (subscriptions_off). |
| 404 | No such model, or the key may not use it (model_not_found). The message says where a model of another kind is served. |
| 413 | The body is over the gateway's limit (request_too_large, 32 MiB by default), or too long for a filter's model (filter_input_too_large). |
| 415 | A Content-Encoding other than gzip or deflate (unsupported_encoding). |
| 429 | Over a limit. Retry-After says when to try again. |
| 502 | The model's backend could not be reached or gave an answer that could not be read (upstream_unavailable, upstream_error), or a filter failed. |
| 503 | No backend serves the model (no_backend), a router or filter could not decide, or the control plane is unavailable. |
| default | Any other error status the model's provider gave, such as 504 or 529, passed on in the caller's error shape. |
Create a text completion¶
POST /api/v1/completions
OpenAI's legacy Completions API, for completion models.
See OpenAI Completions.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
X-Request-Id |
header | string | An id for the request. Kept if it is up to 64 of A-Z a-z 0-9 . _ : -, otherwise replaced. |
X-Client-Request-Id |
header | string | The client's own id, as OpenAI's API takes it: printable ASCII, up to 512 characters. It is logged and sent on to OpenAI models. An invalid one is ignored. |
X-Keera-Client |
header | string | The client's own name, which wins over the User-Agent in reports. |
Content-Encoding |
header | string | The body may be gzip or deflate. One of identity, gzip, x-gzip, deflate. |
Request body
| Field | Type | Description |
|---|---|---|
model |
string | Required. A model or router alias from GET /api/v1/models. |
prompt |
string or array | Required. |
stream |
boolean | Answer as server-sent events (text/event-stream). Default false. |
max_tokens |
integer |
Responses
| Status | Description |
|---|---|
| 200 | The provider's answer, in this API's format. Content: application/json, text/event-stream. |
| 400 | The body or a field is not valid. Codes include invalid_body, missing_model, context_length_exceeded and unsupported_parameter. |
| 401 | No valid credential was sent. |
| 402 | A budget is spent (budget_exceeded), or the organisation's prepaid credit for the deployment's provider keys is (credit_exhausted). Not 429: it does not lift by waiting. |
| 403 | Refused by a filter (filter_refused), a model the organisation may not use until it has paid (org_limited), or a subscription key while the deployment takes none (subscriptions_off). |
| 404 | No such model, or the key may not use it (model_not_found). The message says where a model of another kind is served. |
| 413 | The body is over the gateway's limit (request_too_large, 32 MiB by default), or too long for a filter's model (filter_input_too_large). |
| 415 | A Content-Encoding other than gzip or deflate (unsupported_encoding). |
| 429 | Over a limit. Retry-After says when to try again. |
| 502 | The model's backend could not be reached or gave an answer that could not be read (upstream_unavailable, upstream_error), or a filter failed. |
| 503 | No backend serves the model (no_backend), a router or filter could not decide, or the control plane is unavailable. |
| default | Any other error status the model's provider gave, such as 504 or 529, passed on in the caller's error shape. |
Create embeddings¶
POST /api/v1/embeddings
For embedding models.
See OpenAI Embeddings.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
X-Request-Id |
header | string | An id for the request. Kept if it is up to 64 of A-Z a-z 0-9 . _ : -, otherwise replaced. |
X-Client-Request-Id |
header | string | The client's own id, as OpenAI's API takes it: printable ASCII, up to 512 characters. It is logged and sent on to OpenAI models. An invalid one is ignored. |
X-Keera-Client |
header | string | The client's own name, which wins over the User-Agent in reports. |
Content-Encoding |
header | string | The body may be gzip or deflate. One of identity, gzip, x-gzip, deflate. |
Request body
| Field | Type | Description |
|---|---|---|
model |
string | Required. A model or router alias from GET /api/v1/models. |
input |
string or array | Required. |
Responses
| Status | Description |
|---|---|
| 200 | The provider's answer, in this API's format. |
| 400 | The body or a field is not valid. Codes include invalid_body, missing_model, context_length_exceeded and unsupported_parameter. |
| 401 | No valid credential was sent. |
| 402 | A budget is spent (budget_exceeded), or the organisation's prepaid credit for the deployment's provider keys is (credit_exhausted). Not 429: it does not lift by waiting. |
| 403 | Refused by a filter (filter_refused), a model the organisation may not use until it has paid (org_limited), or a subscription key while the deployment takes none (subscriptions_off). |
| 404 | No such model, or the key may not use it (model_not_found). The message says where a model of another kind is served. |
| 413 | The body is over the gateway's limit (request_too_large, 32 MiB by default), or too long for a filter's model (filter_input_too_large). |
| 415 | A Content-Encoding other than gzip or deflate (unsupported_encoding). |
| 429 | Over a limit. Retry-After says when to try again. |
| 502 | The model's backend could not be reached or gave an answer that could not be read (upstream_unavailable, upstream_error), or a filter failed. |
| 503 | No backend serves the model (no_backend), a router or filter could not decide, or the control plane is unavailable. |
| default | Any other error status the model's provider gave, such as 504 or 529, passed on in the caller's error shape. |
Create a message¶
POST /api/v1/messages
Anthropic's Messages API: the same chat models, routers and guardrails as chat completions, in Anthropic's format, translated where the model is not Anthropic's. Errors come in Anthropic's shape, with type: error.
For a subscription model, Claude Code sends the caller's Claude sign-in in Authorization and the Keera key in X-Keera-Key; the gateway passes Anthropic's anthropic-ratelimit-unified-* headers back. See docs/subscriptions.md.
See Anthropic Messages.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
X-Request-Id |
header | string | An id for the request. Kept if it is up to 64 of A-Z a-z 0-9 . _ : -, otherwise replaced. |
X-Client-Request-Id |
header | string | The client's own id, as OpenAI's API takes it: printable ASCII, up to 512 characters. It is logged and sent on to OpenAI models. An invalid one is ignored. |
X-Keera-Client |
header | string | The client's own name, which wins over the User-Agent in reports. |
Content-Encoding |
header | string | The body may be gzip or deflate. One of identity, gzip, x-gzip, deflate. |
X-Keera-Session |
header | string | Names the session (task) this request belongs to, instead of the one the gateway derives from the opening prompt. X-Session-Id, Helicone-Session-Id and X-Claude-Code-Session-Id are read too; the first with a value wins. See docs/sessions.md. |
anthropic-version |
header | string | Passed on to Anthropic models. Defaults to the version the gateway knows. |
anthropic-beta |
header | string | Beta flags, passed on to Anthropic models. |
beta |
query | string | Sent by Anthropic's SDKs; ignored. |
Request body
| Field | Type | Description |
|---|---|---|
model |
string | Required. A model or router alias from GET /api/v1/models. |
messages |
array of object | Required. |
system |
string or array | |
max_tokens |
integer | Required. |
stream |
boolean | Answer as server-sent events (text/event-stream). Default false. |
tools |
array of object |
Responses
| Status | Description |
|---|---|
| 200 | The provider's answer, in this API's format. Content: application/json, text/event-stream. |
| 400 | The body or a field is not valid. Codes include invalid_body, missing_model, context_length_exceeded, unsupported_parameter, and subscription_model for a subscription model on an API other than this one. |
| 401 | No valid key, or a subscription model without a Claude sign-in (missing_claude_sign_in). |
| 402 | A budget is spent (budget_exceeded), or the organisation's prepaid credit for the deployment's provider keys is (credit_exhausted). Not 429: it does not lift by waiting. |
| 403 | Refused by a filter (filter_refused), a model the organisation may not use until it has paid (org_limited), or a subscription key while the deployment takes none (subscriptions_off). |
| 404 | No such model, or the key may not use it (model_not_found). The message says where a model of another kind is served. |
| 413 | The body is over the gateway's limit (request_too_large, 32 MiB by default), or too long for a filter's model (filter_input_too_large). |
| 415 | A Content-Encoding other than gzip or deflate (unsupported_encoding). |
| 429 | Over a limit. Retry-After says when to try again. |
| 502 | The model's backend could not be reached or gave an answer that could not be read (upstream_unavailable, upstream_error), or a filter failed. |
| 503 | No backend serves the model (no_backend), a router or filter could not decide, or the control plane is unavailable. |
| default | Any other error status the model's provider gave, such as 504 or 529, passed on in the caller's error shape. |
Count a message's tokens¶
POST /api/v1/messages/count_tokens
Answered by the gateway from an estimate, never forwarded: forwarding would send the prompt to Anthropic without its filters. The estimate errs high and includes tools and the system prompts the guardrails and routers add. The model is checked as a real request would check it.
See Anthropic Count Message tokens.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
X-Request-Id |
header | string | An id for the request. Kept if it is up to 64 of A-Z a-z 0-9 . _ : -, otherwise replaced. |
X-Client-Request-Id |
header | string | The client's own id, as OpenAI's API takes it: printable ASCII, up to 512 characters. It is logged and sent on to OpenAI models. An invalid one is ignored. |
X-Keera-Client |
header | string | The client's own name, which wins over the User-Agent in reports. |
Content-Encoding |
header | string | The body may be gzip or deflate. One of identity, gzip, x-gzip, deflate. |
Request body
| Field | Type | Description |
|---|---|---|
model |
string | Required. A model or router alias from GET /api/v1/models. |
messages |
array of object | Required. |
system |
string or array | |
tools |
array of object |
Responses
| Status | Description |
|---|---|
| 200 | The estimate. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | A model the organisation may not use until it has paid (org_limited), or a subscription key while the deployment takes none. |
| 404 | No such model, or the key may not use it. |
| 413 | The body is over the gateway's limit. |
| 415 | A Content-Encoding other than gzip or deflate. |
| 503 | The control plane is unavailable. |
200 response
| Field | Type | Description |
|---|---|---|
input_tokens |
integer |
Create a response¶
POST /api/v1/responses
OpenAI's Responses API: the same chat models, routers and guardrails, translated where the model is not OpenAI's. On the deployment's key, store is turned off.
See OpenAI Responses.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
X-Request-Id |
header | string | An id for the request. Kept if it is up to 64 of A-Z a-z 0-9 . _ : -, otherwise replaced. |
X-Client-Request-Id |
header | string | The client's own id, as OpenAI's API takes it: printable ASCII, up to 512 characters. It is logged and sent on to OpenAI models. An invalid one is ignored. |
X-Keera-Client |
header | string | The client's own name, which wins over the User-Agent in reports. |
Content-Encoding |
header | string | The body may be gzip or deflate. One of identity, gzip, x-gzip, deflate. |
X-Keera-Session |
header | string | Names the session (task) this request belongs to, instead of the one the gateway derives from the opening prompt. X-Session-Id, Helicone-Session-Id and X-Claude-Code-Session-Id are read too; the first with a value wins. See docs/sessions.md. |
Request body
| Field | Type | Description |
|---|---|---|
model |
string | Required. A model or router alias from GET /api/v1/models. |
input |
string or array | Required. |
instructions |
string | |
max_output_tokens |
integer | |
stream |
boolean | Answer as server-sent events (text/event-stream). Default false. |
tools |
array of object |
Responses
| Status | Description |
|---|---|
| 200 | The provider's answer, in this API's format. Content: application/json, text/event-stream. |
| 400 | The body or a field is not valid. Codes include invalid_body, missing_model, context_length_exceeded and unsupported_parameter. |
| 401 | No valid credential was sent. |
| 402 | A budget is spent (budget_exceeded), or the organisation's prepaid credit for the deployment's provider keys is (credit_exhausted). Not 429: it does not lift by waiting. |
| 403 | Refused by a filter (filter_refused), a model the organisation may not use until it has paid (org_limited), or a subscription key while the deployment takes none (subscriptions_off). |
| 404 | No such model, or the key may not use it (model_not_found). The message says where a model of another kind is served. |
| 413 | The body is over the gateway's limit (request_too_large, 32 MiB by default), or too long for a filter's model (filter_input_too_large). |
| 415 | A Content-Encoding other than gzip or deflate (unsupported_encoding). |
| 429 | Over a limit. Retry-After says when to try again. |
| 502 | The model's backend could not be reached or gave an answer that could not be read (upstream_unavailable, upstream_error), or a filter failed. |
| 503 | No backend serves the model (no_backend), a router or filter could not decide, or the control plane is unavailable. |
| default | Any other error status the model's provider gave, such as 504 or 529, passed on in the caller's error shape. |
List models¶
GET /api/v1/models
The models and routers this key may use, in a list both OpenAI's and Anthropic's SDKs read. It is never paged. Locked and decision models are left out.
See OpenAI List models.
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | No valid credential was sent. |
| 403 | A subscription key while the deployment takes none. |
| 503 | The control plane is unavailable. |
200 response
Type: InferenceModelList.
Get a model¶
GET /api/v1/models/{alias}
The alias may hold slashes, as in anthropic/claude-sonnet-4-5, escaped or not: the rest of the path is the alias.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | A model or router alias. It may contain /. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | No valid credential was sent. |
| 403 | A subscription key while the deployment takes none. |
| 404 | No such model, or the key may not use it (model_not_found). |
| 503 | The control plane is unavailable. |
200 response
Type: InferenceModel.
Warm-up probe¶
HEAD /api/api/hello
Some Anthropic-shaped clients send this before their first request. The path is doubled because they append /api/hello to a base URL that already ends in /api. It always answers 200.
No sign-in needed.
Responses
| Status | Description |
|---|---|
| 200 | OK |
MCP¶
MCP servers, behind the same API keys.
Open the server's event stream¶
GET /api/mcp/{alias}
Passed through to the server, which may answer 405 if it has no stream.
See MCP Streamable HTTP transport.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | The MCP server's alias. |
Mcp-Session-Id |
header | string | The MCP session, passed to the server and back. |
Mcp-Protocol-Version |
header | string | Passed to the server. |
Last-Event-Id |
header | string | Resumes a stream, passed to the server. |
Responses
| Status | Description |
|---|---|
| 200 | The server's own messages. Content: text/event-stream. |
| 401 | No valid credential was sent. |
| 403 | The server is in a private network and the organisation has not paid yet (org_limited), or a subscription key while the deployment takes none. |
| 404 | No such server, it is disabled, or the key may not call any of its tools (mcp_server_not_found). Subscription keys reach no MCP server. |
| 502 | The server could not be reached or read (mcp_unavailable), or it refused the gateway's credential for it (mcp_credential_refused). |
| 503 | The control plane is unavailable. |
| default | Any other status the server gave, passed on. |
Send MCP messages¶
POST /api/mcp/{alias}
MCP's Streamable HTTP transport, behind the API key. The body is one JSON-RPC message, or a batch. The gateway lists only the tools the key may call and refuses the others, runs the key's filters over a tool call's arguments, presents the server's own credential and records each call. Everything else is passed through.
A refusal of the gateway's own is a JSON-RPC error with status 200, such as a tool the key may not call, a tool call without an id, or a tool call inside a batch. A key that may call only some tools may send only initialize, ping, tools/*, logging/setLevel and tasks/* other than tasks/list. The client sends Accept: application/json, text/event-stream, as MCP asks.
See MCP Streamable HTTP transport.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | The MCP server's alias. |
Mcp-Session-Id |
header | string | The MCP session, passed to the server and back. |
Mcp-Protocol-Version |
header | string | Passed to the server. |
Mcp-Method |
header | string | Must match the message's method when sent. |
Mcp-Name |
header | string | Must match the tool name or URI in the message when sent. |
X-Keera-Session |
header | string | Names the session (task) this request belongs to, instead of the one the gateway derives from the opening prompt. X-Session-Id, Helicone-Session-Id and X-Claude-Code-Session-Id are read too; the first with a value wins. See docs/sessions.md. |
X-Keera-Client |
header | string | The client's own name, which wins over the User-Agent. |
Request body
Type: JsonRpcMessage or array of JsonRpcMessage.
Responses
| Status | Description |
|---|---|
| 200 | The server's answer, or a JSON-RPC error from the gateway. Content: application/json, text/event-stream. |
| 202 | Accepted: the body held only notifications or responses. |
| 400 | The Mcp-Method or Mcp-Name header does not match the message (JSON-RPC error -32020), or the body could not be read. |
| 401 | No valid credential was sent. |
| 403 | The server is in a private network and the organisation has not paid yet (org_limited), or a subscription key while the deployment takes none. |
| 404 | No such server, it is disabled, or the key may not call any of its tools (mcp_server_not_found). Subscription keys reach no MCP server. |
| 502 | The server could not be reached or read (mcp_unavailable), or it refused the gateway's credential for it (mcp_credential_refused). |
| 503 | The control plane is unavailable. |
| 413 | The body is over the gateway's limit. |
| 415 | A Content-Encoding other than gzip or deflate. |
| default | Any other status the server gave, passed on. A body that is neither JSON nor an event stream is dropped. |
200 response
Type: JsonRpcMessage or array of JsonRpcMessage.
End an MCP session¶
DELETE /api/mcp/{alias}
Passed through to the server.
See MCP Streamable HTTP transport.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | The MCP server's alias. |
Mcp-Session-Id |
header | string | The MCP session, passed to the server and back. |
Mcp-Protocol-Version |
header | string | Passed to the server. |
Responses
| Status | Description |
|---|---|
| 200 | Ended. |
| 401 | No valid credential was sent. |
| 403 | The server is in a private network and the organisation has not paid yet (org_limited), or a subscription key while the deployment takes none. |
| 404 | No such server, it is disabled, or the key may not call any of its tools (mcp_server_not_found). Subscription keys reach no MCP server. |
| 502 | The server could not be reached or read (mcp_unavailable), or it refused the gateway's credential for it (mcp_credential_refused). |
| 503 | The control plane is unavailable. |
| default | Any other status the server gave, such as 204 or 405, passed on. |
Probes¶
Liveness, readiness and metrics, for orchestrators and scrapers.
Liveness¶
GET /healthz
No sign-in needed.
Responses
| Status | Description |
|---|---|
| 200 | The process is up. |
200 response
| Field | Type | Description |
|---|---|---|
status |
ok |
Readiness¶
GET /readyz
Ready when Postgres answers. The reason for a failure goes to the log, not the answer.
No sign-in needed.
Responses
| Status | Description |
|---|---|
| 200 | Ready. |
| 503 | The database did not answer. |
200 response
| Field | Type | Description |
|---|---|---|
status |
ok |
|
usage_written |
integer (int64) | Usage events this process has written. |
usage_dropped |
integer (int64) | Usage events this process has dropped. |
Prometheus metrics¶
GET /metrics
Metrics span every organisation, so only an operator may read them, or a scraper with the metrics token (KEERA_METRICS_TOKEN) as a bearer token. That token reads this route and nothing else.
Responses
| Status | Description |
|---|---|
| 200 | The Prometheus text exposition. Content: text/plain; version=0.0.4; charset=utf-8. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
Sign in¶
Signing in and out of the control API.
How to sign in¶
GET /control/auth/config
What the panel offers on its sign-in screen, before anybody has signed in.
No sign-in needed.
Responses
| Status | Description |
|---|---|
| 200 | The ways in. |
200 response
| Field | Type | Description |
|---|---|---|
sso |
boolean | Whether an identity provider is configured. |
providers |
array of SignInProvider |
|
passkeys |
boolean | Whether passkeys are on. |
Start a sign-in¶
GET /control/auth/login
Opened in a browser. It sends the browser to the identity provider,
or for a password directory or passkeys to the panel's own form.
keera login and Keera Chat add the cli_* parameters, so the
sign-in ends at their address with a one-time code for
POST /control/auth/cli/token.
A sign-in that cannot start also redirects, to /?sign_in_error=1,
with the reason in the keera_sign_in_error cookie.
No sign-in needed.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
provider |
query | string | The identity provider's name, or passkey. It may be left out only when there is one way in. |
next |
query | string | A path on this site to go to afterwards. Anything else is ignored. |
cli_redirect |
query | string (uri) | Where a command-line sign-in ends: a loopback address such as http://127.0.0.1:1234/callback, or Keera Chat's callback under KEERA_CHAT_URL. |
cli_challenge |
query | string | The PKCE S256 challenge, 32 bytes as unpadded base64url. Needed with cli_redirect. |
cli_state |
query | string | Handed back with the code. Needed with cli_redirect; up to 128 characters. |
Responses
| Status | Description |
|---|---|
| 302 | To the identity provider, to the panel's password or passkey form, or back to the sign-in screen with an error. |
| 429 | Over a limit. Retry-After says when to try again. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
Finish a sign-in¶
GET /control/auth/callback
Where the identity provider sends the browser back. It only works in the browser that started the sign-in.
No sign-in needed.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
code |
query | string | |
state |
query | string | |
error |
query | string | Set by the identity provider when it refused. |
error_description |
query | string | |
keera_login |
cookie | string | Set by GET /control/auth/login; must match state. |
Responses
| Status | Description |
|---|---|
| 302 | Signed in, to next or /; for a command-line sign-in, to its address with code and state. Somebody new where sign-up is allowed goes to /?sign_up=1. A failed sign-in goes to /?sign_in_error=1, with the reason in the keera_sign_in_error cookie. |
| 429 | Over a limit. Retry-After says when to try again. |
Sign in with the operator key¶
POST /control/auth/local
Starts a panel session that grants what the operator key grants.
No sign-in needed.
Request body
| Field | Type | Description |
|---|---|---|
key |
string | Required. |
Responses
| Status | Description |
|---|---|
| 200 | Signed in. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 429 | Over a limit. Retry-After says when to try again. |
| 413 | The body is over the size limit. |
200 response
| Field | Type | Description |
|---|---|---|
ok |
true |
Redeem a sign-in code for a CLI token¶
POST /control/auth/cli/token
The last step of keera login. The code works once, and only with
the PKCE verifier of the process that started the sign-in. Send the
token as Authorization: Bearer <token>; GET /control/v1/me says
whose it is.
No sign-in needed.
Request body
| Field | Type | Description |
|---|---|---|
code |
string | Required. |
verifier |
string | Required. |
label |
string | How the machine describes itself, for the audit log. |
Responses
| Status | Description |
|---|---|
| 200 | The token. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 429 | Over a limit. Retry-After says when to try again. |
| 413 | The body is over the size limit. |
200 response
| Field | Type | Description |
|---|---|---|
token |
string | Starts with keera_cli_. Shown only here. |
expires_at |
string (date-time) |
Sign in with a directory password¶
POST /control/auth/password
For an LDAP directory. Attempts are also limited per account, from
any address. Must be sent as application/json.
No sign-in needed.
Request body
| Field | Type | Description |
|---|---|---|
provider |
string | Required. A provider whose password is true. |
flow |
string | The sign-in GET /control/auth/login started for keera login, from the panel's address. Leave it out to sign in to the panel. |
username |
string | Required. |
password |
string (password) | Required. |
next |
string | A path on this site to go to afterwards. |
Responses
| Status | Description |
|---|---|
| 200 | Signed in. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 415 | The body was not sent as application/json. |
| 429 | Over a limit. Retry-After says when to try again. |
| 503 | The directory did not answer. |
| 413 | The body is over the size limit. |
200 response
Type: SignInRedirect.
Start a passkey sign-in¶
POST /control/auth/passkey/options
Starts a new sign-in, or with flow continues the one
GET /control/auth/login started for keera login. Must be sent as
application/json.
No sign-in needed.
Request body
| Field | Type | Description |
|---|---|---|
flow |
string | |
next |
string | A path on this site to go to afterwards. |
Responses
| Status | Description |
|---|---|
| 200 | What to call navigator.credentials.get with. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 415 | The body was not sent as application/json. |
| 429 | Over a limit. Retry-After says when to try again. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
| 413 | The body is over the size limit. |
200 response
| Field | Type | Description |
|---|---|---|
flow |
string | Send it back with the answer. |
options |
RequestOptions |
Sign in with a passkey¶
POST /control/auth/passkey/sign-in
Each flow works once. Must be sent as application/json.
No sign-in needed.
Request body
| Field | Type | Description |
|---|---|---|
flow |
string | Required. |
credential |
Assertion |
Required. |
Responses
| Status | Description |
|---|---|
| 200 | Signed in. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 415 | The body was not sent as application/json. |
| 429 | Over a limit. Retry-After says when to try again. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
| 413 | The body is over the size limit. |
200 response
Type: SignInRedirect.
Start adding a passkey from a set-up link¶
POST /control/auth/passkey/setup/options
Nobody is signed in: the link's token is the credential. Using the
options does not use up the link. Must be sent as application/json.
No sign-in needed.
Request body
| Field | Type | Description |
|---|---|---|
token |
string | Required. The part of the set-up link after #passkey-setup=. |
Responses
| Status | Description |
|---|---|
| 200 | What to call navigator.credentials.create with. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 415 | The body was not sent as application/json. |
| 429 | Over a limit. Retry-After says when to try again. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
| 413 | The body is over the size limit. |
200 response
Type: RegistrationOptions.
Add a passkey from a set-up link¶
POST /control/auth/passkey/setup
Stores the passkey, uses up the link and signs the person in. Must be
sent as application/json.
No sign-in needed.
Request body
| Field | Type | Description |
|---|---|---|
token |
string | Required. The same token as for the options. |
challenge_id |
string | Required. |
name |
string | Defaults to "Passkey". Cut to 64 characters. |
credential |
Registration |
Required. |
Responses
| Status | Description |
|---|---|
| 200 | Signed in. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 409 | It clashes with something that is already there. |
| 415 | The body was not sent as application/json. |
| 429 | Over a limit. Retry-After says when to try again. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
| 413 | The body is over the size limit. |
200 response
Type: SignInRedirect.
Read the sign-up in progress¶
GET /control/auth/signup
For a sign-in that matched no organisation, through a provider that allows sign-up. Says who signed in and who invited them.
No sign-in needed.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
keera_signup |
cookie | string | Required. Set by the callback. |
Responses
| Status | Description |
|---|---|
| 200 | The sign-up. |
| 401 | No valid credential was sent. |
| 429 | Over a limit. Retry-After says when to try again. |
200 response
| Field | Type | Description |
|---|---|---|
email |
string | |
invited_org_name |
string | The organisation that added this address, if any. |
Create or join an organisation¶
POST /control/auth/signup
Creates a limited organisation with the person as its administrator, or joins the one that invited them, and signs them in. A name another organisation has gets the person's address added.
No sign-in needed.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
keera_signup |
cookie | string | Required. Set by the callback. |
Request body
| Field | Type | Description |
|---|---|---|
join |
boolean | Join the inviting organisation instead of creating one. |
name |
string | The new organisation's name. Needed unless join is true. |
Responses
| Status | Description |
|---|---|
| 200 | Signed in. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 409 | It clashes with something that is already there. |
| 429 | Over a limit. Retry-After says when to try again. |
| 413 | The body is over the size limit. |
200 response
Type: SignInRedirect.
Sign out¶
POST /control/auth/logout
Ends this session or CLI token. With all=1, ends every session and
CLI token of this person, in every organisation.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
all |
query | string | 1 to sign out everywhere. One of 1. |
Responses
| Status | Description |
|---|---|
| 200 | Signed out. |
| 401 | No valid credential was sent. |
200 response
| Field | Type | Description |
|---|---|---|
provider_logout_url |
string | The identity provider's own sign-out page, or empty. |
Me¶
The caller and their own access.
Who the caller is¶
GET /control/v1/me
What the panel loads first. A session gets its CSRF token here.
Responses
| Status | Description |
|---|---|
| 200 | The caller and what this deployment offers. |
| 401 | No valid credential was sent. |
200 response
| Field | Type | Description |
|---|---|---|
via |
string | One of operator_key, session, cli. |
user_id |
string | Empty for the operator key. |
email |
string | |
name |
string | |
role |
string | One of operator, admin, member. |
org_id |
string | Empty for the operator key. |
unrestricted |
boolean | Whether the caller is an operator. |
can_admin_org |
boolean | |
csrf |
string | For X-CSRF-Token. Empty unless signed in with a session. |
sso |
boolean | |
providers |
array of SignInProvider |
|
roles_from_directory |
boolean | Whether a directory group decides roles, so they cannot be changed here. |
gateway_url |
string | Where clients send inference requests. |
chat_url |
string | Keera Chat, or empty. |
sandboxes |
boolean | |
billing |
boolean | |
payments |
boolean | |
claude_subscriptions |
boolean | |
passkeys |
boolean | |
passkey_account |
boolean | Whether the caller signs in with passkeys. |
orgs |
array of Membership |
The organisations to switch to, by name. Only when there is more than one. |
org_name |
string | |
org_limited |
boolean | Whether the organisation is limited, as after sign-up. |
Switch organisation¶
POST /control/v1/me/org
Moves the caller's session or CLI token to another of their
organisations. Choosing one that invited them accepts the invitation.
The operator key cannot switch; it names org_id on each request.
Request body
| Field | Type | Description |
|---|---|---|
org_id |
string | Required. |
Responses
| Status | Description |
|---|---|
| 200 | Switched. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It clashes with something that is already there. |
| 413 | The body is over the size limit. |
200 response
| Field | Type | Description |
|---|---|---|
org_id |
string | |
org_name |
string | |
role |
string | One of operator, admin, member. |
My keys, limits and refusals¶
GET /control/v1/access
Only the caller's own keys, even for an administrator, with the limits above each and the refusals of the last week.
Responses
| Status | Description |
|---|---|
| 200 | The caller's access. |
| 401 | No valid credential was sent. |
200 response
| Field | Type | Description |
|---|---|---|
email |
string | |
role |
string | One of operator, admin, member. |
gateway_url |
string | |
keys |
array of AccessKey |
|
refusals |
array of Refusal |
The last 20 refused or failed requests, newest first. |
anonymous |
boolean | True for the operator key, which has no keys. |
operator_key |
boolean | True when signed in to the panel with the operator key. |
org |
object | |
org.id |
string | |
org.name |
string | |
used |
boolean | Whether any of the keys has ever been used. |
Tenancy¶
Organisations, projects, users and passkeys.
List people¶
GET /control/v1/users
An operator who names no organisation sees everyone, by address.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | The people. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of User |
Add a person¶
POST /control/v1/users
Places a person in an organisation before their first sign-in. Needs an administrator of the organisation. A passkey account comes with its first set-up link; an administrator can only create one at the organisation's email domain.
Request body
| Field | Type | Description |
|---|---|---|
org_id |
string | Only an operator, who belongs to none, needs it. |
email |
string (email) | Required. |
external_id |
string | <provider>:<subject>, such as google:1234567890. Only an operator may set it; otherwise the person is linked at their first sign-in. |
role |
string | One of admin, member. Default "member". |
sign_in |
string | Defaults to sso, or passkey on a gateway with passkeys and no identity provider. One of sso, passkey. |
Responses
| Status | Description |
|---|---|
| 201 | Added. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It clashes with something that is already there. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
| 413 | The body is over the size limit. |
201 response
Everything in User, plus:
| Field | Type | Description |
|---|---|---|
passkey_link |
PasskeyLink |
Only for a passkey account. |
Change a person's role¶
PATCH /control/v1/users/{id}
Needs an administrator of the person's organisation. Not allowed where a directory group decides roles, for an operator, or on yourself. Signs the person out, so the new role applies at once.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Request body
| Field | Type | Description |
|---|---|---|
role |
string | Required. One of admin, member. |
Responses
| Status | Description |
|---|---|
| 200 | Changed. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 413 | The body is over the size limit. |
200 response
| Field | Type | Description |
|---|---|---|
id |
string | |
role |
string | One of admin, member. |
Turn a person off¶
POST /control/v1/users/{id}/disable
For someone who left. Revokes their keys, ends their sessions, removes their passkeys and stops their sandboxes. Needs an administrator of their organisation; not for yourself or an operator.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Responses
| Status | Description |
|---|---|
| 200 | Turned off. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
| Field | Type | Description |
|---|---|---|
id |
string | |
email |
string | |
disabled |
true |
|
revoked_keys |
integer | |
sandboxes |
object | Only where this deployment lends out sandboxes. |
sandboxes.terminated |
integer | |
sandboxes.suspended |
integer | An engineer's sandbox is kept, suspended. |
sandboxes.failed |
integer | |
warning |
string | Set when some sandboxes could not be read or stopped. |
Turn a person back on¶
POST /control/v1/users/{id}/enable
They can sign in again. Their old keys stay revoked. Needs an administrator of their organisation; not for yourself or an operator.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Responses
| Status | Description |
|---|---|
| 200 | Turned on. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
| Field | Type | Description |
|---|---|---|
id |
string | |
email |
string | |
disabled |
false |
Hand out a passkey set-up link¶
POST /control/v1/users/{id}/passkey-link
Replaces any earlier link. Turns a person who has not signed in yet into a passkey account. Needs an administrator of their organisation, and, for one, an address at the organisation's email domain.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Responses
| Status | Description |
|---|---|
| 201 | The link. It is shown only here. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It clashes with something that is already there. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
201 response
Type: PasskeyLink.
List passkeys¶
GET /control/v1/passkeys
The caller's own, or with user_id someone the caller administers.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
user_id |
query | string | Needed with the operator key, which has no passkeys. |
Responses
| Status | Description |
|---|---|
| 200 | The passkeys. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of Passkey |
Add a passkey to your account¶
POST /control/v1/passkeys
Finishes what POST /control/v1/passkeys/options started. Only for a
passkey account, in the panel, within 15 minutes of signing in.
Request body
| Field | Type | Description |
|---|---|---|
challenge_id |
string | Required. |
name |
string | Defaults to "Passkey". Cut to 64 characters. |
credential |
Registration |
Required. |
Responses
| Status | Description |
|---|---|
| 201 | Added. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 409 | It clashes with something that is already there. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
| 413 | The body is over the size limit. |
201 response
Type: Passkey.
Start adding a passkey to your account¶
POST /control/v1/passkeys/options
For a second device. Only for a passkey account, in the panel, within
15 minutes of signing in; otherwise the code is sign_in_again or
forbidden. Takes no body.
Responses
| Status | Description |
|---|---|
| 200 | What to call navigator.credentials.create with. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
200 response
Type: RegistrationOptions.
Remove a passkey¶
DELETE /control/v1/passkeys/{id}
Anyone may remove their own, but not their last. An administrator may remove anyone's in their organisation, which also signs that person out.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Responses
| Status | Description |
|---|---|
| 200 | Removed. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It clashes with something that is already there. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
200 response
| Field | Type | Description |
|---|---|---|
id |
string | |
deleted |
true |
List organisations¶
GET /control/v1/orgs
An operator sees every organisation. Anyone else sees only their own.
Responses
| Status | Description |
|---|---|
| 200 | The organisations. |
| 401 | No valid credential was sent. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of Org |
Create an organisation¶
POST /control/v1/orgs
Only an operator may. The new organisation starts with the models and
sandbox classes of the template files, and a project called default.
Request body
| Field | Type | Description |
|---|---|---|
name |
string | Required. |
email_domain |
string | Places a first sign-in from this domain in this organisation. |
Responses
| Status | Description |
|---|---|
| 201 | The new organisation. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 409 | The name or email domain belongs to another organisation (org_name_taken, domain_taken). |
| 413 | The body is over the size limit. |
201 response
Type: Org.
Change an organisation¶
PATCH /control/v1/orgs/{id}
Only an operator may. Send at least one field.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Request body
| Field | Type | Description |
|---|---|---|
name |
string | |
email_domain |
string | An empty string clears it. |
limited |
boolean | false lifts the limit of an organisation that signed up. |
Responses
| Status | Description |
|---|---|
| 200 | The organisation as it is now. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | The name or email domain belongs to another organisation (org_name_taken, domain_taken). |
| 413 | The body is over the size limit. |
200 response
Type: Org.
Delete an organisation¶
DELETE /control/v1/orgs/{id}
Only an operator may, and not on the organisation they are signed in to. It removes the organisation's projects, users and keys. The request log, billing and audit log stay.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Responses
| Status | Description |
|---|---|
| 200 | What the delete removed. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It still has live sandboxes (org_has_sandboxes). Terminate them first. |
200 response
| Field | Type | Description |
|---|---|---|
id |
string | |
name |
string | |
deleted |
true |
|
projects |
integer | How many projects were removed. |
users |
integer | How many users were removed. |
keys |
integer | How many keys were removed. |
List projects¶
GET /control/v1/projects
Each project comes with its guardrails and its spend this period. An
operator who sends no org_id gets every organisation's projects.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | The projects, by name. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of ProjectSummary |
Create a project¶
POST /control/v1/projects
Only an administrator of the organisation may.
Request body
| Field | Type | Description |
|---|---|---|
org_id |
string | The organisation. Only an operator needs it; everyone else creates in their own. |
name |
string | Required. |
description |
string |
Responses
| Status | Description |
|---|---|
| 201 | The new project. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | Another project in the organisation has that name (project_name_taken). |
| 413 | The body is over the size limit. |
201 response
Type: Project.
Rename or describe a project¶
PATCH /control/v1/projects/{id}
Only an administrator of the organisation may. Send at least one field.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Request body
| Field | Type | Description |
|---|---|---|
name |
string | |
description |
string | An empty string clears it. |
Responses
| Status | Description |
|---|---|
| 200 | The project as it is now. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | Another project in the organisation has that name (project_name_taken). |
| 413 | The body is over the size limit. |
200 response
Type: Project.
Delete a project¶
DELETE /control/v1/projects/{id}
Only an administrator of the organisation may. Every key of the project must be revoked first. Revoked keys stay, with no project.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Responses
| Status | Description |
|---|---|
| 200 | The project is gone. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It still has keys that are not revoked (project_has_keys), or live sandboxes (project_has_sandboxes). |
200 response
| Field | Type | Description |
|---|---|---|
id |
string | |
name |
string | |
deleted |
true |
|
detached_keys |
integer | How many revoked keys stayed behind, now in no project. |
Keys¶
API keys.
List API keys¶
GET /control/v1/keys
Each key comes with its own guardrails, last use and spend since since.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
project_id |
query | string | Only this project's keys. |
since |
query | string | How far back to count spend, as a duration such as 24h. The default is the start of this month (UTC). It is rounded down to midnight. |
Responses
| Status | Description |
|---|---|
| 200 | The keys. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of KeySummary |
|
since |
string (date-time) | Where the counts start. |
Issue an API key¶
POST /control/v1/keys
Only an administrator of the organisation may. The secret is in key
and is shown only this once. A pending key has no secret yet: the
person it is for gets one when they activate it.
Request body
| Field | Type | Description |
|---|---|---|
org_id |
string | The organisation. Only an operator needs it. |
project_id |
string | Leave it out for the organisation's oldest project. |
user_id |
string | The person whose spend this is. Required for a subscription key and for a pending key. |
name |
string | Defaults to unnamed. |
kind |
KeyKind |
|
expires_in_seconds |
integer (int64) | Lifetime from now. Not with expires_at. Neither means it never expires. |
expires_at |
string (date-time) | When it expires; must be in the future. Not with expires_in_seconds. |
pending |
boolean | Leave the key for the person named in user_id to activate. |
Responses
| Status | Description |
|---|---|
| 201 | The new key. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | The person or project is not in this organisation. |
| 409 | The organisation has no projects yet (project_required). |
| 501 | A subscription key, while this deployment takes no Claude subscriptions (subscriptions_disabled). |
| 413 | The body is over the size limit. |
201 response
Everything in KeyInfo, plus:
| Field | Type | Description |
|---|---|---|
key |
string | The secret. Shown only now. Absent for a pending key. |
Rename an API key¶
PATCH /control/v1/keys/{id}
An administrator may, and so may a member for a key in their own name.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Request body
| Field | Type | Description |
|---|---|---|
name |
string | Required. |
Responses
| Status | Description |
|---|---|
| 200 | The new name. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 413 | The body is over the size limit. |
200 response
| Field | Type | Description |
|---|---|---|
id |
string | |
name |
string |
Revoke an API key¶
DELETE /control/v1/keys/{id}
An administrator may, and so may a member for a key in their own name. The key stays listed, revoked, so its usage still has a name.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Responses
| Status | Description |
|---|---|
| 200 | The key no longer works. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It was already revoked (key_revoked). |
200 response
| Field | Type | Description |
|---|---|---|
id |
string | |
revoked |
true |
Rotate an API key¶
POST /control/v1/keys/{id}/rotate
Issues a new key with the old one's project, person, kind and
guardrails, and revokes the old one. An administrator may, and so may a
member for a key in their own name, but only an administrator may set
its lifetime. The secret is in key and is shown only this once.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Request body
| Field | Type | Description |
|---|---|---|
name |
string | Empty keeps the old name. |
expires_in_seconds |
integer (int64) | Lifetime from now. Not with expires_at. Neither gives the new key the old one's lifetime, counted from now. |
expires_at |
string (date-time) | When it expires; must be in the future. |
Responses
| Status | Description |
|---|---|
| 201 | The new key. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It is revoked (key_revoked), not yet activated (key_pending), or belongs to a sandbox (sandbox_key). |
| 501 | A subscription key, while this deployment takes no Claude subscriptions (subscriptions_disabled). |
| 413 | The body is over the size limit. |
201 response
Everything in KeyInfo, plus:
| Field | Type | Description |
|---|---|---|
key |
string | The secret. Shown only now. |
replaced_key_id |
string | The old key, now revoked. |
Activate a pending API key¶
POST /control/v1/keys/{id}/activate
Gives a pending key its secret. The person it is for may, and so may an
administrator. The secret is in key and is shown only this once.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string |
Request body
| Field | Type | Description |
|---|---|---|
name |
string | A new name, such as the machine's. Empty keeps it. |
Responses
| Status | Description |
|---|---|
| 200 | The key, now active. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It is revoked (key_revoked), expired (key_expired) or already active (key_active). |
| 501 | A subscription key, while this deployment takes no Claude subscriptions (subscriptions_disabled). |
| 413 | The body is over the size limit. |
200 response
Everything in KeyInfo, plus:
| Field | Type | Description |
|---|---|---|
key |
string | The secret. Shown only now. |
Guardrails¶
Limits and rules per organisation, project or key.
Read a guardrail¶
GET /control/v1/guardrails/{scope}/{id}
What this one scope sets on its own. A scope with no guardrail answers an empty one. Anyone in the organisation may read it.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
scope |
path | ScopeType |
The level the guardrail is on. |
id |
path | string | The id of the organisation, project or key. |
Responses
| Status | Description |
|---|---|
| 200 | The scope's own guardrail. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
Type: Limits.
Set a guardrail¶
PUT /control/v1/guardrails/{scope}/{id}
Replaces the scope's guardrail with the body. Only an administrator of
the organisation may. On an organisation, only an operator may change
allowed_repos; an administrator who leaves it out keeps it.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
scope |
path | ScopeType |
The level the guardrail is on. |
id |
path | string | The id of the organisation, project or key. |
Request body
Type: Limits.
Responses
| Status | Description |
|---|---|
| 200 | The guardrail as stored, cleaned up. |
| 400 | The guardrail is not valid, or names a filter or MCP server the organisation does not have (filter_not_found, mcp_server_not_found). |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | An organisation's allow-list leaves out a model that one of its filters or routers runs on (filter_model_not_allowed, router_model_not_allowed). |
| 413 | The body is over the size limit. |
200 response
Type: Limits.
Read the guardrails that apply¶
GET /control/v1/guardrails/{scope}/{id}/effective
Every level above the scope, and what they combine to, as the gateway combines them. It answers "why is this key refused". Anyone in the organisation may read it.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
scope |
path | ScopeType |
The level the guardrail is on. |
id |
path | string | The id of the organisation, project or key. |
Responses
| Status | Description |
|---|---|
| 200 | The levels and their result. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
Type: Effective.
Filters¶
List filters¶
GET /control/v1/filters
Every member may read it, instructions included. With stats=1 it also
says what each filter did in the window.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
stats |
query | string | "1" adds stats, from and to. One of 1. |
from |
query | string (date-time) | Start of the stats window. Defaults to 30 days ago. |
to |
query | string (date-time) | End of the stats window. Defaults to now. |
since |
query | string | The window as a duration back from now, such as 24h. |
Responses
| Status | Description |
|---|---|
| 200 | The organisation's filters. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of Filter |
|
stats |
map of FilterStat |
By alias. A filter that did not run is left out. |
from |
string (date-time) | |
to |
string (date-time) |
Create or replace a filter¶
PUT /control/v1/filters/{alias}
Administrators only. A rewrite or gate filter needs model and
prompt, and no rules. A pattern filter needs rules, and no
model or prompt. See docs/filters.md.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | Lowercase letters, digits and inner hyphens, up to 64. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Request body
| Field | Type | Description |
|---|---|---|
model |
string | The model the filter runs on: a chat model, or a decision model for a gate. The organisation must allow it. |
mode |
string | Empty or left out means rewrite. One of rewrite, gate, pattern. |
shadow |
boolean | Run, record, but enforce nothing. Left out means enforce. |
prompt |
string | The instruction for the filter's model, up to 16 KiB. |
rules |
array of FilterRule |
|
description |
string |
Responses
| Status | Description |
|---|---|
| 200 | The filter as saved. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 413 | The body is over the size limit. |
200 response
Type: Filter.
Delete a filter¶
DELETE /control/v1/filters/{alias}
Administrators only. A filter that a router step or a guardrail still uses is refused with 409.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | Lowercase letters, digits and inner hyphens, up to 64. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | Deleted. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It clashes with something that is already there. |
200 response
Type: AliasDeleted.
Run a filter over a sample¶
POST /control/v1/filters/{alias}/check
Administrators only. Sends a fixed sample through the filter and shows
what came back. A filter that fails still answers 200, with ok false.
It runs the filter's model, so it can cost money.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | What the check found. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
Type: FilterProbe.
One filter's report¶
GET /control/v1/filters/{alias}/report
Every member may read it. A deleted filter keeps its report; then
filter is left out.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
from |
query | string (date-time) | Defaults to 30 days ago. |
to |
query | string (date-time) | Defaults to now. |
since |
query | string | The window as a duration back from now, such as 24h. |
Responses
| Status | Description |
|---|---|
| 200 | The report. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
| Field | Type | Description |
|---|---|---|
report |
FilterReport |
|
filter |
Filter |
|
project_names |
map of string | Project names by id. |
Routers¶
List routers¶
GET /control/v1/routers
Every member may read it. With stats=1 it also says what went
through each router in the window.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
stats |
query | string | "1" adds stats, from and to. One of 1. |
from |
query | string (date-time) | Start of the stats window. Defaults to 30 days ago. |
to |
query | string (date-time) | End of the stats window. Defaults to now. |
since |
query | string | The window as a duration back from now, such as 24h. |
Responses
| Status | Description |
|---|---|
| 200 | The organisation's routers. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of Router |
|
stats |
map of RouterStat |
By alias. A router nobody called is left out. |
from |
string (date-time) | |
to |
string (date-time) |
Create or replace a router¶
PUT /control/v1/routers/{alias}
Administrators only. The alias may not be one of the organisation's
models (409). A new router is put on the organisation's allow-list,
if it has one, unless the list leaves out a model the router reaches;
warning then says so. See docs/routers.md.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | Lowercase letters, digits and inner hyphens, up to 64. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Request body
| Field | Type | Description |
|---|---|---|
display_name |
string | |
description |
string | |
start |
RouterStep |
Required. |
Responses
| Status | Description |
|---|---|
| 200 | The router as saved. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It clashes with something that is already there. |
| 413 | The body is over the size limit. |
200 response
Type: SavedRouter.
Delete a router¶
DELETE /control/v1/routers/{alias}
Administrators only. It also comes off the organisation's allow-list. A router that a project's or key's allow-list still names is refused with 409.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | Lowercase letters, digits and inner hyphens, up to 64. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | Deleted. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It clashes with something that is already there. |
200 response
Type: AliasDeleted.
Check a router's route steps¶
POST /control/v1/routers/{alias}/check
Administrators only. An instruction step is given sample prompts and
shows where each went; the other modes ask their destinations whether
they answer. It can cost money. A router with no route step, or no
step named by step, answers 400.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
step |
query | string | Check only the route step with this id. |
Responses
| Status | Description |
|---|---|
| 200 | One probe per route step checked. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
Type: RouterCheck.
One router's report¶
GET /control/v1/routers/{alias}/report
Every member may read it. A deleted router keeps its report; then
router is left out.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
from |
query | string (date-time) | Defaults to 30 days ago. |
to |
query | string (date-time) | Defaults to now. |
since |
query | string | The window as a duration back from now, such as 24h. |
Responses
| Status | Description |
|---|---|
| 200 | The report. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
| Field | Type | Description |
|---|---|---|
report |
RouterReport |
|
router |
Router |
Models¶
List models¶
GET /control/v1/models
Anyone signed in may read it. Only the organisation's administrators
see backends (null for everyone else) and has_api_key. With
stats=1 it adds each model's time to first token in the window.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
stats |
query | string | "1" adds stats, from and to. One of 1. |
from |
query | string (date-time) | Start of the stats window. Defaults to 30 days ago. |
to |
query | string (date-time) | End of the stats window. Defaults to now. |
since |
query | string | The window as a duration back from now, such as 24h. |
Responses
| Status | Description |
|---|---|
| 200 | The organisation's models. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of OrgModel |
|
stats |
map of ModelStat |
By alias. A model with no answers is left out. |
from |
string (date-time) | |
to |
string (date-time) |
List hosted providers¶
GET /control/v1/providers
The hosted providers this build knows, with their models and prices.
Anyone signed in may read it; only an administrator sees endpoint
(empty for everyone else).
Responses
| Status | Description |
|---|---|
| 200 | The providers. |
| 401 | No valid credential was sent. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of HostedProvider |
Create or replace a model¶
PUT /control/v1/models/{alias}
Administrators only. A new model is put on the organisation's
allow-list, if it has one. For a provider whose key the deployment
holds, the backend and prices are set by the gateway and api_key
must be left out. See docs/providers.md.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | Lowercase letters, digits and inner hyphens, with optional slashes between parts, such as anthropic/claude-sonnet-4-5. Send a slash as %2F. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Request body
| Field | Type | Description |
|---|---|---|
kind |
string | Defaults to chat. One of chat, completion, embedding, decision. |
backends |
array of string (uri) | http or https base URLs, such as http://vllm:8000/v1. At least one, unless the deployment holds the provider's key. |
backend_model |
string | Required. The name the backend serves the model under. |
provider |
string | A hosted provider from GET /control/v1/providers, or empty for a self-served model. |
display_name |
string | |
description |
string | |
input_micros_per_mtok |
integer (int64) | Micro-units per million input tokens. |
output_micros_per_mtok |
integer (int64) | Micro-units per million output tokens. |
cached_input_micros_per_mtok |
integer (int64) | 0 means the input price. |
cache_write_micros_per_mtok |
integer (int64) | 0 means the input price. |
long_prompt |
PriceTier |
|
max_context_tokens |
integer | |
release_date |
string (date) | YYYY-MM-DD. |
location |
string | Where the model runs, such as ch, usa or onprem. Left out, it comes from the provider or the backend address. |
subscription |
boolean | Paid by each caller's own Claude plan. Needs provider anthropic, kind chat, and no api_key. See docs/subscriptions.md. |
enabled |
boolean | Left out, a new model starts on and an existing one keeps its state. |
api_key |
string | The backend's credential. Left out keeps the stored one, and "" removes it. Needed again when backends moves to another host. |
alias |
string | Ignored; the path names the model. |
org_id |
string | Ignored. |
has_api_key |
boolean | Ignored. |
platform_key |
boolean | Ignored. |
Responses
| Status | Description |
|---|---|
| 200 | The model as stored. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It clashes with something that is already there. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
| 413 | The body is over the size limit. |
200 response
Type: OrgModel.
Delete a model¶
DELETE /control/v1/models/{alias}
Administrators only. It also comes off every allow-list in the organisation. A model a router step still uses is refused with 409.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | Lowercase letters, digits and inner hyphens, with optional slashes between parts, such as anthropic/claude-sonnet-4-5. Send a slash as %2F. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | Deleted. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It clashes with something that is already there. |
200 response
Type: AliasDeleted.
Check a model works¶
POST /control/v1/models/{alias}/check
Administrators only. Calls the model's own backend with a tool and a
question only that tool can answer. A model that fails still answers
200, with ok false. It can cost money.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | Send a slash as %2F. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | What the check found. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
Type: ModelProbe.
Send a chat from the panel¶
POST /control/v1/playground/chat
Sends one OpenAI-shaped chat completion with one of the caller's own
API keys, named by key_id. It goes through the same data plane as
POST /api/v1/chat/completions: that key's guardrails, budgets and
usage apply, and the answer is the same, streamed as server-sent
events when the body asks for stream. The operator key cannot use
it, nor can a subscription key.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
key_id |
query | string | Required. One of the caller's own API keys. |
X-Keera-Client |
header | string | Only keera-chat is kept, and only from a CLI sign-in. Every other request is recorded as keera-playground. |
Request body
| Field | Type | Description |
|---|---|---|
model |
string | Required. A model or router alias the key may call. |
messages |
array of object | Required. |
stream |
boolean |
Responses
| Status | Description |
|---|---|
| 200 | The completion, as the inference API answers it. Content: application/json, text/event-stream. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 402 | The model is on the deployment's key and the organisation has no credit left. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It clashes with something that is already there. |
| 429 | Over a limit. Retry-After says when to try again. |
| 502 | The model's backend failed or could not be reached. |
| 413 | The body is over the size limit. |
200 response
Type: object. An OpenAI chat completion.
MCP servers¶
List MCP servers¶
GET /control/v1/mcp-servers
Anyone in the organisation may read it. Only its administrators see
url (empty for everyone else), auth_header and has_api_key.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | The organisation's MCP servers. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of MCPServer |
Create or replace an MCP server¶
PUT /control/v1/mcp-servers/{alias}
Administrators only. See docs/mcp.md.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | Lowercase letters, digits and inner hyphens, up to 64. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Request body
| Field | Type | Description |
|---|---|---|
url |
string (uri) | Required. The server's Streamable HTTP endpoint, http or https. |
description |
string | |
auth_header |
string | The header the credential goes in. Empty means Authorization, as a bearer token; any other header gets the credential as it is. |
enabled |
boolean | Left out, a new server starts on and an existing one keeps its state. |
api_key |
string | Left out keeps the stored one, and "" removes it. Needed again when url moves to another host. |
Responses
| Status | Description |
|---|---|
| 200 | The server as stored. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 413 | The body is over the size limit. |
200 response
Type: MCPServer.
Delete an MCP server¶
DELETE /control/v1/mcp-servers/{alias}
Administrators only. A server a guardrail's tool allow-list still names is refused with 409.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
alias |
path | string | Lowercase letters, digits and inner hyphens, up to 64. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | Deleted. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | It clashes with something that is already there. |
200 response
Type: AliasDeleted.
List tool calls¶
GET /control/v1/tool-calls
Administrators only, for one organisation. Newest first, a page at a
time. With summary=1 it adds up the calls of each tool instead,
most called first, with no paging.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
from |
query | string (date-time) | Defaults to 30 days ago. |
to |
query | string (date-time) | Defaults to now. |
since |
query | string | The window as a duration back from now, such as 24h. |
project_id |
query | string | |
key_id |
query | string | |
user_id |
query | string | |
server |
query | string | An MCP server's alias. |
tool |
query | string | |
limit |
query | integer | Rows per page. Defaults to 100, at most 5000. |
before |
query | integer (int64) | The next_before of the previous page. |
summary |
query | string | "1" adds up the calls per tool. One of 1. |
Responses
| Status | Description |
|---|---|
| 200 | The calls, or with summary=1 the totals per tool. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
One of:
Calls
| Field | Type | Description |
|---|---|---|
from |
string (date-time) | |
to |
string (date-time) | |
data |
array of ToolCallRow |
|
next_before |
integer (int64) | Pass as before for the next page. Left out on an empty page. |
Summary
| Field | Type | Description |
|---|---|---|
from |
string (date-time) | |
to |
string (date-time) | |
data |
array of ToolSummary |
Connect¶
Setting up clients against this gateway.
List the clients to connect¶
GET /control/v1/connect
The coding agents that can be pointed at this gateway, with their templates, and the gateway address to put in them. Anyone signed in may read it.
Responses
| Status | Description |
|---|---|
| 200 | The client catalogue. |
| 401 | No valid credential was sent. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of ConnectClient |
|
gateway_url |
string (uri) | The inference API's base URL, as a browser reaches it. |
Fill in every client's configuration¶
POST /control/v1/connect/render
Renders each client's block for the given models, the default first.
No key is sent: a block that holds one carries placeholder instead.
Request body
| Field | Type | Description |
|---|---|---|
models |
array of ConnectModel |
The models and routers the key may call, the default first. |
Responses
| Status | Description |
|---|---|
| 200 | One rendered block per client. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 413 | The body is over the size limit. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of object | |
data[].client |
string | The client's id. |
data[].config |
string | The filled-in block. |
data[].run |
string | What to type once it is configured. |
placeholder |
string | The text that stands in a block for the key. |
List the caller's own clients¶
GET /control/v1/clients
Which clients called with the caller's own keys in the window, and
with which key, newest first. Even an administrator sees only their
own. The window is widened to whole days. The operator key has no
keys, so it gets an empty list and anonymous.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
from |
query | string (date-time) | Defaults to 30 days ago. |
to |
query | string (date-time) | Defaults to now. |
since |
query | string | The window as a duration back from now, such as 24h. |
Responses
| Status | Description |
|---|---|
| 200 | The clients. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
200 response
| Field | Type | Description |
|---|---|---|
from |
string (date-time) | |
to |
string (date-time) | |
data |
array of ConnectedClient |
|
anonymous |
boolean | The caller is not a person, so has no keys. |
Reports¶
Usage, requests, sessions, audit and diagnostics.
Check the deployment¶
GET /control/v1/diagnostics
What a deployment is missing, as keera doctor shows it. Nothing is written. An administrator sees their organisation's checks; the deployment's own checks are only for operators.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
probe |
query | string | 1 also calls each enabled model. Only for the organisation's administrators, and needs one organisation. One of 1. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
Type: Diagnosis.
Count what the deployment has¶
GET /control/v1/setup
For the panel's first-run steps. Without an organisation, an operator gets counts across all of them.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
Type: SetupState.
Dashboard figures¶
GET /control/v1/overview
The dashboard in one call. Narrowed to a project, key, person or model, it is that thing's own screen.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
from |
query | string (date-time) | Start of the window. Default: 30 days before to. |
to |
query | string (date-time) | End of the window. Default: now. |
since |
query | string | The window as a duration back from now, such as 24h. from wins over it. |
project_id |
query | string | Only this project's requests. |
key_id |
query | string | Only this key's requests. |
user_id |
query | string | Only this person's requests. A member may only name themselves. |
alias |
query | string | Only this model, as clients name it. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
| Field | Type | Description |
|---|---|---|
overview |
Overview |
|
project_names |
map of string | Project names by id. |
key_names |
map of string | Key names by id. Only on a narrowed overview. |
Traffic between clients and models¶
GET /control/v1/map
The deployment as a picture: the clients that called, every model of the organisation, and the traffic between them. A member sees only the traffic of their own keys.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
from |
query | string (date-time) | Start of the window. Default: 30 days before to. |
to |
query | string (date-time) | End of the window. Default: now. |
since |
query | string | The window as a duration back from now, such as 24h. from wins over it. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
from |
string (date-time) | |
to |
string (date-time) | |
gateway_url |
string | The inference API's address, for the middle of the drawing. |
gateway |
UsageCell |
Every request in the window. |
clients |
array of MapClient |
Busiest first. |
models |
array of MapModel |
Every model of the organisation, busiest first. |
flows |
array of MapFlow |
|
last_minute |
array of integer (int64) | Requests finished in each of the last 60 seconds, the most recent second first. |
Usage report¶
GET /control/v1/usage
Requests, tokens and cost per group. Over more than two days it counts whole days, and from and to say so.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
from |
query | string (date-time) | Start of the window. Default: 30 days before to. |
to |
query | string (date-time) | End of the window. Default: now. |
since |
query | string | The window as a duration back from now, such as 24h. from wins over it. |
group_by |
query | string | What each row is. Default model. One of client, day, key, model, org, project, user. |
project_id |
query | string | Only this project's requests. |
key_id |
query | string | Only this key's requests. |
user_id |
query | string | Only this person's requests. A member may only name themselves. |
alias |
query | string | Only this model, as clients name it. |
format |
query | string | csv answers a spreadsheet instead. One of csv. |
Responses
| Status | Description |
|---|---|
| 200 | The report, or with format=csv a spreadsheet. Content: application/json, text/csv. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
Everything in ReportLabels, plus:
| Field | Type | Description |
|---|---|---|
from |
string (date-time) | |
to |
string (date-time) | |
data |
array of UsageBucket |
|
chart |
UsageChart |
|
client_names |
map of string | Client labels by client key. Only with group_by=client. |
Audit log¶
GET /control/v1/audit
Who changed what, newest first. Administrators only. Without from, to or since it has no time window.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
actor |
query | string | Only entries by this actor. |
action |
query | string | Only this action, such as billing.topup. |
from |
query | string (date-time) | Start of the window. Default: 30 days before to. |
to |
query | string (date-time) | End of the window. Default: now. |
since |
query | string | The window as a duration back from now, such as 24h. from wins over it. |
limit |
query | integer | Entries per page. Default 100, at most 5000. |
before |
query | integer (int64) | The next_before of the previous page. |
format |
query | string | csv answers a spreadsheet of every matching entry instead, up to 5000. One of csv. |
Responses
| Status | Description |
|---|---|
| 200 | The report, or with format=csv a spreadsheet. Content: application/json, text/csv. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of AuditEntry |
|
filters |
AuditFacets |
First page only. |
next_before |
integer (int64) | Pass as before for the next page. Absent on an empty page. |
Request log¶
GET /control/v1/requests
One row per inference request, whatever happened to it. An administrator reads the whole organisation's; a member only the requests made with their own keys.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
from |
query | string (date-time) | Start of the window. Default: 30 days before to. |
to |
query | string (date-time) | End of the window. Default: now. |
since |
query | string | The window as a duration back from now, such as 24h. from wins over it. |
project_id |
query | string | Only this project's requests. |
key_id |
query | string | Only this key's requests. |
user_id |
query | string | Only this person's requests. A member may only name themselves. |
alias |
query | string | Only this model, as clients name it. |
outcome |
query | string | Only requests with this outcome. unhappy is everything that did not deliver. One of ok, unhappy, failed, refused, interrupted. |
status |
query | string | One exact status, such as 402, or a class, such as 5xx. Anything else filters nothing. |
request_id |
query | string | One request, by the id the gateway answered with. It ignores the window. |
sort |
query | string | The order. Default recent, newest first. One of recent, cost, tokens, ttft. |
limit |
query | integer | Rows per page. Default 100, at most 5000. |
before |
query | integer (int64) | The next_before of the previous page. Paging works only in the default order. |
format |
query | string | csv answers a spreadsheet of the whole filtered window instead, up to 5000 rows. One of csv. |
facets |
query | string | 1 adds filters, the values this window can be narrowed by. First page only. One of 1. |
Responses
| Status | Description |
|---|---|
| 200 | The report, or with format=csv a spreadsheet. Content: application/json, text/csv. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
Everything in ReportLabels, plus:
| Field | Type | Description |
|---|---|---|
from |
string (date-time) | |
to |
string (date-time) | |
data |
array of RequestLogEntry |
|
outcomes |
RequestOutcomes |
Counts over the whole window. First page only. |
filters |
RequestFacets |
With facets=1, first page only. |
next_before |
integer (int64) | Pass as before for the next page. Only in the default order, and absent on an empty page. |
Request log, live¶
GET /control/v1/requests/stream
The rows GET /control/v1/requests returns, narrowed the same way, as they are written. The response stays open.
Each batch is an event named requests whose data is a JSON object:
data: the new rows, newest first, at most 200 (RequestLogEntry).outcomes: the counts over the windowsincegives (RequestOutcomes).next_after: the newest id sent. Pass it asafterto resume.project_names,key_names,user_names,user_full_names: only when a row names something the earlier labels did not.
An idle stream sends a : keepalive comment every 25 seconds. It ends when the server shuts down; reconnect with after.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
from |
query | string (date-time) | Start of the window. Default: 30 days before to. |
to |
query | string (date-time) | End of the window. Default: now. |
since |
query | string | The window as a duration back from now, such as 24h. from wins over it. |
project_id |
query | string | Only this project's requests. |
key_id |
query | string | Only this key's requests. |
user_id |
query | string | Only this person's requests. A member may only name themselves. |
alias |
query | string | Only this model, as clients name it. |
outcome |
query | string | Only requests with this outcome. unhappy is everything that did not deliver. One of ok, unhappy, failed, refused, interrupted. |
status |
query | string | One exact status, such as 402, or a class, such as 5xx. Anything else filters nothing. |
request_id |
query | string | One request, by the id the gateway answered with. It ignores the window. |
after |
query | integer (int64) | The newest id the reader already has. Default: the end of the log. |
Responses
| Status | Description |
|---|---|
| 200 | A stream of server-sent events. Content: text/event-stream. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
Sessions¶
GET /control/v1/sessions
The request log grouped into tasks. A member sees only the sessions of their own keys. See docs/sessions.md.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
from |
query | string (date-time) | Start of the window. Default: 30 days before to. |
to |
query | string (date-time) | End of the window. Default: now. |
since |
query | string | The window as a duration back from now, such as 24h. from wins over it. |
project_id |
query | string | Only this project's requests. |
key_id |
query | string | Only this key's requests. |
user_id |
query | string | Only this person's requests. A member may only name themselves. |
alias |
query | string | Only this model, as clients name it. |
session_key |
query | string | Only this conversation. |
outcome |
query | string | Only sessions holding a request with this outcome. ok keeps those where every request was served. One of ok, unhappy, failed, refused, interrupted. |
status |
query | string | How the session ended: the last request's status, such as 402, or a class, such as 5xx. |
unhappy |
query | string | 1 is outcome=unhappy, for older keera commands. One of 1. |
sort |
query | string | The order. Default recent, newest first. One of recent, cost, tokens, requests, duration. |
limit |
query | integer | Rows per page. Default 100, at most 5000. |
before |
query | integer (int64) | The next_before of the previous page. Paging works only in the default order. |
format |
query | string | csv answers a spreadsheet of the whole filtered window instead, up to 5000 sessions. One of csv. |
facets |
query | string | 1 adds filters, the values this window can be narrowed by. First page only. One of 1. |
Responses
| Status | Description |
|---|---|
| 200 | The report, or with format=csv a spreadsheet. Content: application/json, text/csv. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
Everything in ReportLabels, plus:
| Field | Type | Description |
|---|---|---|
from |
string (date-time) | |
to |
string (date-time) | |
data |
array of AgentSession |
|
gap_seconds |
integer (int64) | The idle gap that cut these sessions. |
totals |
AgentSessionTotals |
Over the whole window. First page only. |
filters |
RequestFacets |
With facets=1, first page only. |
next_before |
integer (int64) | Pass as before for the next page. Only in the default order, and absent on an empty page. |
One session¶
GET /control/v1/sessions/{id}
One task from start to end. A member sees it only if every request in it was made with their own keys.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | integer (int64) | The id of any request in the session. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
Everything in ReportLabels, plus:
| Field | Type | Description |
|---|---|---|
session |
AgentSession |
|
requests |
array of RequestLogEntry |
Every request, in order. |
tool_calls |
array of ToolCallRow |
The MCP tool calls in between, oldest first. |
gap_seconds |
integer (int64) | The idle gap that cut this session. |
Billing¶
Monthly bill¶
GET /control/v1/billing
One month of use of the deployment's own provider keys, per organisation and provider model, at list price. Administrators only. See docs/billing.md.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
month |
query | string | The month, in UTC. Default: the current one. |
format |
query | string | csv answers a spreadsheet instead. One of csv. |
Responses
| Status | Description |
|---|---|
| 200 | The report, or with format=csv a spreadsheet. Content: application/json, text/csv. |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
month |
string | |
from |
string (date-time) | |
to |
string (date-time) | |
data |
array of BillingRow |
|
total |
BillingTotal |
|
payments |
boolean | Whether this deployment takes payments in advance. |
Credit account¶
GET /control/v1/billing/account
The organisation's credit, its settings and its latest payments. Administrators only. It first looks up payments started in the last hour, so one just paid shows at once. On a deployment that takes no payments, it answers only {"payments": false}.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
payments |
boolean | Whether this deployment takes payments. Every other field is there only when it does. |
vat_bp |
integer (int64) | VAT added to every payment, in basis points. |
fee_fixed_micros |
integer (int64) | The fixed part of the transaction fee. |
fee_bp |
integer (int64) | The share of the credit added to the fee, in basis points. |
fee_min_micros |
integer (int64) | The least the share adds to the fee. |
topup_min_micros |
integer (int64) | The smallest payment. |
topup_max_micros |
integer (int64) | The largest payment. |
account |
CreditAccount |
|
recent |
array of Payment |
The latest 20 payments, newest first. |
Change the automatic top-up¶
PATCH /control/v1/billing/account
Sets the automatic top-up, and for operators whether the organisation is billed by invoice. Administrators only. Changing the top-up clears why the last one failed.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Request body
| Field | Type | Description |
|---|---|---|
topup_below_micros |
integer (int64) | Below this balance the saved card is charged. Send with topup_micros; both 0 turn the top-up off. |
topup_micros |
integer (int64) | What one top-up pays, from CHF 1 in whole centimes, and at least topup_below_micros. |
invoiced |
boolean | Bill by invoice, so no credit is needed. Operators only. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 413 | The body is too large. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
200 response
Type: CreditAccount.
Remove the saved card¶
DELETE /control/v1/billing/account/card
Here and at PostFinance. The automatic top-up stops with it. Administrators only.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
200 response
| Field | Type | Description |
|---|---|---|
org_id |
string | |
deleted |
true |
Pay for credit¶
POST /control/v1/billing/topups
Starts a card payment and answers the payment page to send the person to. The credit is added once PostFinance says it is paid. VAT and a transaction fee come on top. Administrators only.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Request body
| Field | Type | Description |
|---|---|---|
amount_micros |
integer (int64) | Required. The credit to buy, from CHF 1 to CHF 10,000, in whole centimes. |
save_card |
boolean | Save the card for automatic top-ups. |
Responses
| Status | Description |
|---|---|
| 201 | Started |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 413 | The body is too large. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
| 502 | PostFinance Checkout could not start the payment. Nothing was charged. The code is payment_unavailable. |
201 response
| Field | Type | Description |
|---|---|---|
payment |
Payment |
The pending payment. Its created_at is not filled in. |
url |
string (uri) | PostFinance Checkout's payment page. |
List payments¶
GET /control/v1/billing/payments
Newest first. Administrators only; an operator without org_id sees every organisation's.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
limit |
query | integer | Payments per page. Default 50, at most 500. |
before |
query | string | The next_before of the previous page: a payment id. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of Payment |
|
next_before |
string | Pass as before for the next page. Absent on an empty page. |
Receipt of a payment¶
GET /control/v1/billing/payments/{id}/receipt
The invoice PostFinance renders for a paid payment. Administrators only. A grant has none.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
id |
path | string | The payment id. |
Responses
| Status | Description |
|---|---|
| 200 | The receipt. Content: application/pdf. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | The payment has no receipt: it is a grant, or not paid. The code is no_receipt. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
| 502 | PostFinance Checkout did not hand out the receipt. The code is receipt_unavailable. |
Grant credit¶
POST /control/v1/billing/grants
Adds credit without a payment, or takes some away: a trial, a refund, money that came by bank transfer. Operators only.
Request body
| Field | Type | Description |
|---|---|---|
org_id |
string | Required. The organisation to credit. |
amount_micros |
integer (int64) | Required. The credit to add, or below 0 to take away. Not 0, and at most CHF 10,000 either way. |
note |
string | Required. Why. It shows in the organisation's payments. |
Responses
| Status | Description |
|---|---|
| 201 | Granted |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 413 | The body is too large. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
201 response
Type: Payment.
PostFinance webhook¶
POST /control/billing/postfinance
PostFinance Checkout says a transaction changed. The call proves nothing: the gateway only reads the transaction back over PostFinance's own API. Anything that is not one of its transactions is acknowledged and ignored. Throttled per address.
No sign-in needed.
Request body
| Field | Type | Description |
|---|---|---|
entityId |
integer (int64) | The transaction id. |
listenerEntityTechnicalName |
string | |
spaceId |
integer (int64) | The PostFinance space. |
Responses
| Status | Description |
|---|---|
| 200 | Handled or ignored. No body. |
| 400 | The body is not JSON. |
| 404 | This deployment takes no payments. Content: text/plain. |
| 429 | Over a limit. Retry-After says when to try again. |
| 503 | The transaction could not be read. PostFinance tries again later. |
| 413 | The body is over the size limit. |
Sandboxes¶
List sandbox classes¶
GET /control/v1/sandbox-classes
Open to anyone in the organisation. Also gives the driver, when there is one, and the sandbox limits of the organisation's first project, so a client can grey out what creating a sandbox would refuse.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of SandboxClass |
|
driver |
object | Absent when the deployment runs no sandbox driver. |
driver.name |
string | |
driver.isolation |
SandboxIsolation |
The strongest tier the driver can deliver. |
driver.tiers |
array or null | Every tier the driver can deliver. |
limits |
ResolvedSandbox |
Create or replace a sandbox class¶
PUT /control/v1/sandbox-classes/{name}
Administrators only. The body has the shape the list returns, so a class read can be written back; org_id, created_at and updated_at are ignored and the name comes from the path. Zero or empty fields take their defaults (isolation isolated). An administrator may only use the images of the starting classes, those KEERA_SANDBOX_IMAGES allows, or the image the class already has.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
name |
path | string | The class name: lowercase letters, digits and hyphens. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Request body
| Field | Type | Description |
|---|---|---|
org_id |
string | |
name |
string | |
description |
string | |
image |
string | Required. |
isolation |
SandboxIsolation |
|
cpu_millis |
integer | |
memory_mib |
integer | |
disk_mib |
integer | |
default_ttl_seconds |
integer | |
max_ttl_seconds |
integer | |
warm |
integer | |
purposes |
array of SandboxPurpose |
|
created_at |
string (date-time) | |
updated_at |
string (date-time) |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | Not an administrator, an image the caller may not use, or a warm pool for an organisation that has not paid yet (org_limited). |
| 413 | The body is too large. |
200 response
Type: SandboxClass.
Delete a sandbox class¶
DELETE /control/v1/sandbox-classes/{name}
Administrators only. A class in use can be deleted: running sandboxes keep their own copy of it. The answer says how many are still running.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
name |
path | string | The class name: lowercase letters, digits and hyphens. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
200 response
| Field | Type | Description |
|---|---|---|
name |
string | |
deleted |
true |
|
live_sandboxes |
integer |
List sandboxes¶
GET /control/v1/sandboxes
A member sees only their own. An administrator sees the whole organisation's. Finished sandboxes are left out unless all=1.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
project_id |
query | string | Only this project's. |
class |
query | string | Only this class. |
purpose |
query | SandboxPurpose |
Only engineer or agent sandboxes. |
user_id |
query | string | Only this person's. Administrators only; a member always gets their own. |
all |
query | string | Include finished sandboxes. Only 1 counts. One of 1. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | No valid credential was sent. |
| 403 | Another organisation, or an account that is not a person. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of Sandbox |
Create a sandbox¶
POST /control/v1/sandboxes
The sandbox gets an API key of its own, in the project it lands in. A member cannot choose the project: it is the organisation's oldest. An organisation that signed itself up waits for its first payment. The answer comes before the machine is ready; its state is pending.
Request body
| Field | Type | Description |
|---|---|---|
org_id |
string | Only an operator needs it. |
project_id |
string | Administrators only. |
name |
string | Required. Lowercase letters, digits and interior hyphens. It becomes a hostname. |
class |
string | Required. |
purpose |
SandboxPurpose |
Defaults to engineer. |
ttl_seconds |
integer | The lifetime asked for. Left out, the class default. Above a ceiling, it is cut down, not refused. |
repo |
string | What to check out. An agent sandbox needs one. |
branch |
string | |
task |
string | The instruction an agent sandbox starts on. It is never stored. |
authorized_keys |
array of string | The ssh public keys that may open a shell. An engineer's sandbox needs at least one. |
env |
map of string | Extra environment. It cannot name a KEERA_ variable. |
Responses
| Status | Description |
|---|---|
| 201 | Created |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | A member chose a project, or the organisation has not paid yet (org_limited). |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | Refused: no such class, a name already in use, or over a quota (sandbox_state). |
| 413 | The body is too large. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
201 response
Type: Sandbox.
Get a sandbox¶
GET /control/v1/sandboxes/{ref}
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
ref |
path | string | The sandbox's id (sbx_...) or its name. A name is unique only for one person in one organisation, so an operator naming one also passes org_id. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | More than one person has a live sandbox of that name (sandbox_ambiguous). |
200 response
Type: Sandbox.
Terminate a sandbox¶
DELETE /control/v1/sandboxes/{ref}
The machine goes; the row stays, so its cost still resolves. An administrator may terminate any sandbox in their organisation.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
ref |
path | string | The sandbox's id (sbx_...) or its name. A name is unique only for one person in one organisation, so an operator naming one also passes org_id. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | The sandbox's state does not allow it, or the manager refused. The code is sandbox_state, sandbox_not_ready or sandbox_ambiguous. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
200 response
| Field | Type | Description |
|---|---|---|
id |
string | |
name |
string | |
terminated |
true |
Suspend a sandbox¶
POST /control/v1/sandboxes/{ref}/suspend
Releases the compute and keeps the volume. It finishes in the background, so the answer is the state read back now, not the state asked for.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
ref |
path | string | The sandbox's id (sbx_...) or its name. A name is unique only for one person in one organisation, so an operator naming one also passes org_id. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | The sandbox's state does not allow it, or the manager refused. The code is sandbox_state, sandbox_not_ready or sandbox_ambiguous. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
200 response
Type: Sandbox.
Resume a sandbox¶
POST /control/v1/sandboxes/{ref}/resume
Brings back a suspended or expired sandbox. An expired one gets a new key and lifetime, under the guardrail as it is now. It finishes in the background, so the answer is the state read back now.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
ref |
path | string | The sandbox's id (sbx_...) or its name. A name is unique only for one person in one organisation, so an operator naming one also passes org_id. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | The sandbox's state does not allow it, or the manager refused. The code is sandbox_state, sandbox_not_ready or sandbox_ambiguous. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
200 response
Type: Sandbox.
Extend a sandbox's lifetime¶
POST /control/v1/sandboxes/{ref}/extend
Sets the expiry to now plus the lifetime asked for, cut down to the class's and the guardrail's ceilings. No body is the same as {}: the class default.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
ref |
path | string | The sandbox's id (sbx_...) or its name. A name is unique only for one person in one organisation, so an operator naming one also passes org_id. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Request body
| Field | Type | Description |
|---|---|---|
ttl_seconds |
integer |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | The sandbox has expired (resume it instead) or is no longer live (sandbox_state). |
| 413 | The body is too large. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
200 response
| Field | Type | Description |
|---|---|---|
id |
string | |
name |
string | |
expires_at |
string (date-time) |
Report sandbox usage¶
GET /control/v1/sandbox-usage
Administrators only. Sandbox time grouped by class, project or person. A sandbox counts whole in the window it was created in. The window is the last 30 days unless from, to or since say otherwise.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
group_by |
query | string | How to group. Defaults to class. One of class, project, user. Default "class". |
from |
query | string (date-time) | Start of the window, RFC 3339. |
to |
query | string (date-time) | End of the window, RFC 3339. Defaults to now. |
since |
query | string | A duration back from now, such as 24h. from wins over it. |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 400 | The request is not valid. |
| 401 | No valid credential was sent. |
| 403 | The caller may not do this. |
200 response
| Field | Type | Description |
|---|---|---|
data |
array of SandboxUsage |
|
group_by |
string | One of class, project, user. |
from |
string (date-time) | |
to |
string (date-time) |
Open a byte stream to a sandbox port¶
GET /sandbox/v1/{ref}/tcp/{port}
An HTTP/1.1 upgrade, not JSON. The gateway authenticates the caller as the control API does, dials the port inside the sandbox, answers 101 and copies bytes both ways until one end stops. A connection idle for two hours is closed. Port 2222 is the sandbox's ssh server; keera sandbox ssh and keera sandbox proxy are the clients.
Only the sandbox's owner or an operator may attach. An administrator can see and terminate a colleague's sandbox but not open it.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
ref |
path | string | The sandbox's id (sbx_...) or its name. A name is unique only for one person in one organisation, so an operator naming one also passes org_id. |
port |
path | integer | An unprivileged port inside the sandbox. 2222 is ssh. |
org_id |
query | string | The organisation to act on. Only an operator, who belongs to none, needs it; everyone else acts on their own. |
Upgrade |
header | keera-sandbox/1 |
Required. The protocol token. It is versioned, because the keera command may be older than the gateway. |
Connection |
header | Upgrade |
Required. |
Responses
| Status | Description |
|---|---|
| 101 | Switching protocols. From here on the connection carries the port's raw bytes. |
| 400 | The port is not between 1024 and 65535, or an operator named a sandbox without an organisation. |
| 401 | No valid credential was sent. |
| 403 | The sandbox is somebody else's. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | The sandbox's state does not allow it, or the manager refused. The code is sandbox_state, sandbox_not_ready or sandbox_ambiguous. |
| 426 | The Upgrade header is missing or names another protocol (upgrade_required). A proxy in front may have stripped it. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
Get a fresh repository credential¶
POST /sandbox/v1/git-credential
Asked for by a sandbox, not a person: git-credential-keera calls it when the token it holds has five minutes left. It authenticates with the sandbox's own API key (KEERA_API_KEY) as a bearer token, so it stops working when the sandbox ends or its owner is disabled. The replaced token is revoked where the forge allows it. There is no request body.
The answer is in the format git's credential helpers speak. It takes 10 requests a minute per client address on each replica.
Responses
| Status | Description |
|---|---|
| 200 | The credential, as key=value lines. password_expiry_utc is a Unix time, and only there when the token runs out. Content: text/plain. |
| 401 | No key, or a key that belongs to no live sandbox. |
| 404 | There is no such thing, or the caller may not see it. |
| 409 | Nothing to refresh: the sandbox has no repository, is not running or has run out of time (sandbox_state), or the forge gave no token (no_git_credential). |
| 429 | Over a limit. Retry-After says when to try again. |
| 501 | This deployment has the feature turned off. The code ends in _disabled. |
Spec¶
This description, as YAML or JSON.
This spec, as YAML¶
GET /openapi.yaml
No sign-in needed.
Responses
| Status | Description |
|---|---|
| 200 | The spec, with info.version set to the running build. Content: application/yaml. |
This spec, as JSON¶
GET /openapi.json
No sign-in needed.
Responses
| Status | Description |
|---|---|
| 200 | The spec, with info.version set to the running build. |