API reference
Team
Team: per-person console sign-in (sessions), members, API keys and webhook settings.
Console sign-in: start (public, used by the console)
GET/v1/console/auth/{provider}/start
Used by the console server / browser, not by the SDKs. The console generates a PKCE verifier and passes
challenge = base64url(SHA-256(verifier)); the control plane records the state (10 minutes, single use) and redirects with 302 to the Google / GitHub consent page.
return_to must be under the configured console URL, otherwise 400. If sign-in with this provider is not configured, 503 login_unavailable.
Rate-limited per IP (30 per minute); beyond that, 429 rate_limited.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
challengerequired | query | string | The base64url S256 PKCE challenge (43–128 characters). |
return_torequired | query | string (uri) | Where in the console to return after sign-in (usually the console's |
Responses
- 302Redirect to the provider's consent page
- 400Invalid parameters: validation failed, the body is not valid JSON, or the query string could not be parsed (all returned in the same JSON shape).
- 404Does not exist, or does not belong to this developer (`no such resource`); for a nonexistent route `message` is `no such route`
- 429A count limit was reached (this is not rate limiting; retrying will not help until something is released)
- 503Temporarily unable to complete the request; `error` tells the cases apart: `environment_not_placed` (not placed on a host within 15 seconds), `environment_not_running` (not up within 90 seconds), `host_unavailable` (host offline), `guest_unavailable` (the VM cannot be reached or disconnected midway), `exec_failed` (the process could not be started inside the VM), `exec_timeout` (a synchronous exec still had not finished after its timeout), `file_operation_failed` (a file operation hit an unexpected error).
Console sign-in: provider callback (public)
GET/v1/console/auth/{provider}/callback
Not for developers to call: the callback URL registered with Google / GitHub. If the state is valid, the code is exchanged for the provider's token and the account and verified email are fetched
(Google: email_verified; GitHub: the primary email or any verified email from /user/emails). On first sign-in the account is linked to the member by email
(a member already linked to a different account is not relinked), then a 302 redirects to return_to:
on success with code (60 seconds, single use, usable only together with the PKCE verifier from start); on failure with error:
provider_denied (the user cancelled at the provider), provider_error (token exchange failed), email_not_verified, not_a_member,
identity_in_use (the member matching the email is already linked to a different account), rate_limited (more than 10 attempts for the same email within 10 minutes).
If the state is missing, expired or already used, there is nowhere to redirect, so 400 is returned.
Parameters
| Parameter | In | Type | Description |
|---|---|---|---|
state | query | string | |
code | query | string | |
error | query | string |
Responses
- 302Redirect to `return_to` with `code` or `error`
- 400Invalid parameters: validation failed, the body is not valid JSON, or the query string could not be parsed (all returned in the same JSON shape).
- 404Does not exist, or does not belong to this developer (`no such resource`); for a nonexistent route `message` is `no such route`
- 429A count limit was reached (this is not rate limiting; retrying will not help until something is released)
- 503Temporarily unable to complete the request; `error` tells the cases apart: `environment_not_placed` (not placed on a host within 15 seconds), `environment_not_running` (not up within 90 seconds), `host_unavailable` (host offline), `guest_unavailable` (the VM cannot be reached or disconnected midway), `exec_failed` (the process could not be started inside the VM), `exec_timeout` (a synchronous exec still had not finished after its timeout), `file_operation_failed` (a file operation hit an unexpected error).
Console sign-in: exchange the code for a session token (used by the console server)
POST/v1/console/sessions
Called by the console server with the code from the callback and the PKCE verifier from start (no API key). Returns a session token temper_cs_...
(this one time only; the console stores it in an HttpOnly cookie). After that, Authorization: Bearer temper_cs_... works on every /v1 endpoint just like an API key,
but identifies the member; it expires after 12 hours idle or 7 days after sign-in.
If the member belongs to several developers (teams) and no developer_id is given, no team is chosen for them: 409 choose_developer with a developers list. The code is not consumed;
choose a team and resend with developer_id. Code expired, used, or verifier mismatch: 400 invalid_code. Not a member of that team: 403. Rate-limited per IP.
Request body application/json · CreateConsoleSessionRequest
| Field | Type | Description |
|---|---|---|
coderequired | string | |
verifierrequired | string | The PKCE verifier for the challenge sent at start. |
developer_id | string | null | Which team to choose when the member belongs to several. |
Responses
- 201Session
ConsoleSession - 400Invalid parameters: validation failed, the body is not valid JSON, or the query string could not be parsed (all returned in the same JSON shape).
- 403The agent user inside the runtime unit lacks permission (cannot read, write, or create the parent directories)
- 409`choose_developer`: choose a team and try again
- 415Missing `Content-Type: application/json` (`unsupported_media_type`)
- 422The JSON does not match the schema: a missing field, a wrong type or enum value, or an unknown field (`invalid_body`)
- 429A count limit was reached (this is not rate limiting; retrying will not help until something is released)
- 500Internal error (details are recorded only in server logs)
Switch team (get a session bound to a different team)
POST/v1/console/sessions/switch
Sessions only (an API key gets 403 session_required). The old session is invalidated and a new one is returned (the "recent sign-in" time carries over). Not a member of that team: 403.
Request body application/json
| Field | Type | Description |
|---|---|---|
developer_idrequired | string |
Responses
- 201New session
ConsoleSession - 401Missing, invalid or revoked key
- 403The agent user inside the runtime unit lacks permission (cannot read, write, or create the parent directories)
- 415Missing `Content-Type: application/json` (`unsupported_media_type`)
- 422The JSON does not match the schema: a missing field, a wrong type or enum value, or an unknown field (`invalid_body`)
- 500Internal error (details are recorded only in server logs)
Sign out (invalidate the current session)
DELETE/v1/console/session
Sessions only (an API key gets 403 session_required).
Responses
- 204Signed out
- 401Missing, invalid or revoked key
- 403The agent user inside the runtime unit lacks permission (cannot read, write, or create the parent directories)
- 500Internal error (details are recorded only in server logs)
Who am I
GET/v1/me
The current developer (team), member (null when using an API key) and authentication method; with a session, also every team this account can access (for switching).
Responses
- 200Current identity
Me - 401Missing, invalid or revoked key
- 500Internal error (details are recorded only in server logs)
Team members
GET/v1/members
Both sessions and API keys can list members. Sorted by join time; removed members are not listed.
Responses
- 200Members
MemberList - 401Missing, invalid or revoked key
- 500Internal error (details are recorded only in server logs)
Add a member
POST/v1/members
Only an owner session can add members (an API key gets 403 session_required, a member gets 403 forbidden). The new member signs in with the Google / GitHub account for this email.
Emails are stored in lowercase; an existing member gives 409 member_exists; a role other than owner / member, or a malformed email, gives 400.
Request body application/json · AddMemberRequest
| Field | Type | Description |
|---|---|---|
emailrequired | string | |
rolerequired | MemberRole |
|
name | string | null |
Responses
- 201Added
Member - 400Invalid parameters: validation failed, the body is not valid JSON, or the query string could not be parsed (all returned in the same JSON shape).
- 401Missing, invalid or revoked key
- 403The agent user inside the runtime unit lacks permission (cannot read, write, or create the parent directories)
- 409State conflict; `error` holds the specific code
- 415Missing `Content-Type: application/json` (`unsupported_media_type`)
- 422The JSON does not match the schema: a missing field, a wrong type or enum value, or an unknown field (`invalid_body`)
- 500Internal error (details are recorded only in server logs)
Change a member's role
PATCH/v1/members/{id}
Only an owner session can change roles. The last owner cannot be demoted to member (409 last_owner).
Request body application/json
| Field | Type | Description |
|---|---|---|
rolerequired | MemberRole |
|
Responses
- 200Changed
Member - 400Invalid parameters: validation failed, the body is not valid JSON, or the query string could not be parsed (all returned in the same JSON shape).
- 401Missing, invalid or revoked key
- 403The agent user inside the runtime unit lacks permission (cannot read, write, or create the parent directories)
- 404Does not exist, or does not belong to this developer (`no such resource`); for a nonexistent route `message` is `no such route`
- 409State conflict; `error` holds the specific code
- 415Missing `Content-Type: application/json` (`unsupported_media_type`)
- 422The JSON does not match the schema: a missing field, a wrong type or enum value, or an unknown field (`invalid_body`)
- 500Internal error (details are recorded only in server logs)
Remove a member
DELETE/v1/members/{id}
Only an owner session that signed in within the last 10 minutes can remove members (otherwise 403 reauth_required). The removed person's console sessions stop working immediately.
The last owner cannot be removed (409 last_owner).
Responses
- 204Removed
- 401Missing, invalid or revoked key
- 403The agent user inside the runtime unit lacks permission (cannot read, write, or create the parent directories)
- 404Does not exist, or does not belong to this developer (`no such resource`); for a nonexistent route `message` is `no such route`
- 409State conflict; `error` holds the specific code
- 500Internal error (details are recorded only in server logs)
List API keys
GET/v1/api-keys
Both sessions and API keys can list keys (including revoked ones); the keys themselves are never returned.
Responses
- 200API key
ApiKeyList - 401Missing, invalid or revoked key
- 500Internal error (details are recorded only in server logs)
Issue an API key
POST/v1/api-keys
Only an owner session that signed in within the last 10 minutes can issue keys (an API key cannot issue new keys: 403 session_required; otherwise 403 reauth_required).
key appears only in this response; only its hash is stored. Names are 1–100 characters.
Request body application/json
| Field | Type | Description |
|---|---|---|
namerequired | string |
Responses
- 201New key (`key` is shown only this once)
CreatedApiKey - 400Invalid parameters: validation failed, the body is not valid JSON, or the query string could not be parsed (all returned in the same JSON shape).
- 401Missing, invalid or revoked key
- 403The agent user inside the runtime unit lacks permission (cannot read, write, or create the parent directories)
- 415Missing `Content-Type: application/json` (`unsupported_media_type`)
- 422The JSON does not match the schema: a missing field, a wrong type or enum value, or an unknown field (`invalid_body`)
- 500Internal error (details are recorded only in server logs)
Revoke an API key
DELETE/v1/api-keys/{id}
Takes effect immediately and is idempotent (an already revoked key is returned unchanged). A key can revoke itself (to disable it at once if it leaks); revoking another key requires an owner session that signed in within the last 10 minutes. Returns the revoked record (200).
Responses
- 200The revoked record
ApiKey - 401Missing, invalid or revoked key
- 403The agent user inside the runtime unit lacks permission (cannot read, write, or create the parent directories)
- 404Does not exist, or does not belong to this developer (`no such resource`); for a nonexistent route `message` is `no such route`
- 500Internal error (details are recorded only in server logs)
Get the webhook URL
GET/v1/webhook
Both sessions and API keys can read it. The signing secret is not returned. previous_secret_expires_at is non-null after a secret rotation while the old secret has not yet expired.
Responses
- 200Webhook settings
Webhook - 401Missing, invalid or revoked key
- 500Internal error (details are recorded only in server logs)
Set the webhook URL / rotate the signing secret
PUT/v1/webhook
Only an owner session that signed in within the last 10 minutes can change it. Setting it for the first time, changing the URL, or rotate_secret: true
issues a new whsec_ secret (shown only in this response); re-saving the same URL returns secret as null. After a rotation the old secret stays valid for 24 hours, during which every
webhook carries two signatures, new and old (Temper-Signature: t=…,v1=<new>,v1=<old>; signature verification in all three SDKs accepts multiple v1 values).
The URL must be https:// (http:// is only for local testing), otherwise 400.
Request body application/json · SetWebhookRequest
| Field | Type | Description |
|---|---|---|
urlrequired | string (uri) | |
rotate_secret | boolean |
Responses
- 200Set
SetWebhookResponse - 400Invalid parameters: validation failed, the body is not valid JSON, or the query string could not be parsed (all returned in the same JSON shape).
- 401Missing, invalid or revoked key
- 403The agent user inside the runtime unit lacks permission (cannot read, write, or create the parent directories)
- 415Missing `Content-Type: application/json` (`unsupported_media_type`)
- 422The JSON does not match the schema: a missing field, a wrong type or enum value, or an unknown field (`invalid_body`)
- 500Internal error (details are recorded only in server logs)