Quick start
Create an API token in the dashboard, then use it as a bearer token on every REST, streaming, and MCP request. Every operation below carries its authentication rule, parameters, request schema, response schema, status codes, and a copyable example.
export RIVETPLANE_TOKEN="your-api-token" curl "https://rivetplane.com/v1/sessions?status=waiting_approval" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
HTTP 202 means the control plane relayed a command to the runner. Completion arrives later through session state, transcript events, or the event stream.
The OpenAPI 3.1 document at /openapi.json is the source of truth for this page and for /llms.txt.
Authentication
Supabase Auth owns browser signup, login, email confirmation, and user sessions. Rivetplane issues separate hashed bearer tokens for remote consumers and for paired runners.
| Credential | Header or cookie | Issued by | Can do |
|---|---|---|---|
| API token | Authorization: Bearer … | Dashboard, POST /api-tokens | Every REST, SSE, WebSocket, and MCP operation for its account, with read and write scope |
| Machine token | Authorization: Bearer … | POST /v1/auth/token | Only the relay channel and its own machine's sessions. It cannot retire a runner. |
| Browser session | hcp_access, hcp_refresh, hcp_csrf cookies | Supabase sign-in | Dashboard pages and forms. Every state change also needs the matching csrf field. |
Read scope covers list and get operations. Write scope covers messages, responses, interrupts, session creation, and runner retirement. A WebSocket client that cannot set headers may send the subprotocol bearer.<base64url-token>.
Authorization: Bearer YOUR_API_TOKEN
Session identifiers
Remote IDs are namespaced, so routing stays unambiguous across machines:
{machine_id}/{harness_type}/{local_session_id}Percent-encode the complete value when it appears in a REST path or in an MCP resource URI.
https://rivetplane.com/v1/sessions/runner-01%2Fcodex%2Fses_01
Service
Returns 200 as soon as the HTTP listener serves traffic. Deployment health checks use it.
Authentication: None. The endpoint is public.
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | Health | The server is live |
Example request
curl "https://rivetplane.com/health"
Example response
{
"ok": true
}
The machine-readable description of this API. The /docs page and llms.txt are rendered from the same definitions and the same capability catalog.
Authentication: None. The endpoint is public.
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | object | An OpenAPI 3.1 document with schemas and examples |
Example request
curl "https://rivetplane.com/openapi.json"
Example response
{
"openapi": "3.1.0",
"info": {
"title": "Rivetplane API",
"version": "0.1.0"
},
"paths": {
"/v1/sessions": {
"get": {
"summary": "List visible sessions, newest activity first."
}
}
}
}
A plain-text summary of authentication, endpoints, request bodies, responses, streams, and MCP tools for agent consumers.
Authentication: None. The endpoint is public.
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/plain | string | Plain-text API summary |
Example request
curl "https://rivetplane.com/llms.txt"
Example response
# Rivetplane ## Authentication Authorization: Bearer <API_TOKEN> ...
The rendered reference. Every operation on it comes from the same definitions as /openapi.json.
Authentication: None. The endpoint is public.
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/html | Html | The API reference page |
Example request
curl "https://rivetplane.com/docs"
Example response
<!doctype html> ... Rivetplane API ...
A one-page map of every REST, SSE, WebSocket, and MCP capability, with purpose, parameters, limits, and examples for each one.
Authentication: None. The endpoint is public.
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/html | Html | The capability overview page |
Example request
curl "https://rivetplane.com/capabilities"
Example response
<!doctype html> ... Everything on the plane. ...
A small script that adds a copy button to every code block. The content security policy allows this file only.
Authentication: None. The endpoint is public.
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/javascript | string | JavaScript for the documentation, capability, and dashboard pages |
Example request
curl "https://rivetplane.com/ui.js"
Example response
(() => { /* copy buttons */ })();
Machines
Lists every runner the token can see. An API token sees the account fleet; a machine token sees only itself. Retired runners are excluded.
Authentication: Bearer token in the Authorization header. Scope: read.
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | Machine[] | Machines visible to the token |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
Example request
curl "https://rivetplane.com/v1/machines" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
[
{
"id": "runner-01",
"name": "Studio Mac",
"owner_account_id": "account-01",
"last_seen_at": "2026-08-26T10:15:00Z",
"status": "online",
"harnesses": [
"codex",
"opencode"
]
}
]
Hides a duplicate or dead runner and its sessions from list views. Transcript history stays readable by direct session ID. Machine tokens cannot retire a runner.
Authentication: Bearer token in the Authorization header. Scope: write.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
machine_id | string | required | The runner to retire. Example: runner-01 |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | MachineRetired | The runner is retired |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 404 | application/json | Error | The target does not exist. |
| 409 | application/json | Error | The runner is offline or the target state changed. |
Example request
curl -X POST "https://rivetplane.com/v1/machines/runner-01/retire" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
{
"retired": true
}
Pairing
Issues a secret device code and a short user code. The server stores only SHA-256 hashes. Requests are rate limited per connection address.
Authentication: None. The endpoint is public.
Request body (application/json)
Stable controller identity and display name.
| Field | Type | Requirement | Description |
|---|---|---|---|
device_name | string | required | Controller name. (min length 1) |
device_id | string | required | Stable controller identity. (format uuid) |
{
"device_name": "Desk controller",
"device_id": "4f61c8c8-55c3-4a36-b8eb-11855267982e"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | DeviceCodeResponse | A device authorization request |
| 400 | application/json | Error | Invalid device name or identity |
| 429 | application/json | Error | Too many device authorization requests |
Example request
curl -X POST "https://rivetplane.com/v1/auth/device/code" \
-H "Content-Type: application/json" \
-d '{"device_name":"Desk controller","device_id":"4f61c8c8-55c3-4a36-b8eb-11855267982e"}'
Example response
{
"device_code": "base64url-secret",
"user_code": "ABCD-EFGH",
"verification_uri": "https://rivetplane.com/device",
"expires_in": 600,
"interval": 5
}
Polls at the returned interval. Approval produces one account-scoped consumer token. The exchange is single-use and replay safe. Early polls increase the interval by five seconds.
Authentication: None. The endpoint is public.
Request body (application/json)
The secret device code.
| Field | Type | Requirement | Description |
|---|---|---|---|
device_code | string | required | Secret device code from the issue response. |
{
"device_code": "base64url-secret"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | DeviceTokenResponse | A minimum-scope consumer token |
| 400 | application/json | DeviceTokenError | Pending, slow down, expired, denied, or invalid/replayed grant |
| 429 | application/json | Error | Too many device authorization requests |
Example request
curl -X POST "https://rivetplane.com/v1/auth/device/token" \
-H "Content-Type: application/json" \
-d '{"device_code":"base64url-secret"}'
Example response
{
"error": "authorization_pending",
"interval": 5
}
Lists controller identities and status. Consumer tokens cannot use this token-management endpoint.
Authentication: Bearer token in the Authorization header. Scope: read.
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | ConsumerDevice[] | Paired controllers |
| 401 | application/json | Error | Missing or invalid bearer token |
| 403 | application/json | Error | An account API token is required |
Example request
curl "https://rivetplane.com/v1/consumer-devices" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
[]
Revokes the device and every active token for it. Consumer tokens cannot revoke devices.
Authentication: Bearer token in the Authorization header. Scope: write.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
device_id | string | required | Consumer device record ID. |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | Revoked | The controller was revoked |
| 401 | application/json | Error | Missing or invalid bearer token |
| 403 | application/json | Error | An account API token is required |
| 404 | application/json | Error | Consumer device not found |
Example request
curl -X POST "https://rivetplane.com/v1/consumer-devices/6c2fd71b-e082-4aca-b8ea-f52704ccd501/revoke" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
{
"revoked": true
}
The last step of local-client pairing. The local client opens /authorize in a browser, receives a code on its loopback callback, and exchanges it here. Every exchange revokes the earlier tokens of that machine.
Authentication: One-time authorization code plus the PKCE code verifier.
Request body (application/json)
The code, the PKCE verifier, and the same loopback callback.
| Field | Type | Requirement | Description |
|---|---|---|---|
code | string | required | Authorization code from the loopback callback. |
code_verifier | string | required | PKCE verifier for the S256 challenge. |
redirect_uri | string | required | The exact loopback callback used in the authorization request. |
{
"code": "one-time-code",
"code_verifier": "pkce-verifier",
"redirect_uri": "http://127.0.0.1:43123/callback"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | MachineTokenResponse | A machine bearer token |
| 400 | application/json | object | invalid_grant: unknown, expired, or replayed code, wrong redirect URI, or wrong verifier |
Example request
curl -X POST "https://rivetplane.com/v1/auth/token" \
-H "Content-Type: application/json" \
-d '{"code":"one-time-code","code_verifier":"pkce-verifier","redirect_uri":"http://127.0.0.1:43123/callback"}'
Example response
{
"access_token": "shown-once",
"token_type": "Bearer",
"machine_id": "runner-01",
"owner_account_id": "account-01"
}
The local client opens this page in the browser. It shows what the runner may do and posts the approval. An anonymous visitor is redirected to /login with return_to preserved.
Authentication: Browser session cookies plus a CSRF form field.
Query parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
redirect_uri | string | required | Loopback callback. Only http://127.0.0.1:PORT/callback or the IPv6 loopback is allowed. Example: http://127.0.0.1:45678/callback |
state | string | required | Opaque client state that is returned on the callback. Example: b7f1c2 |
code_challenge | string | required | PKCE S256 challenge, 43 base64url characters. Example: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM |
code_challenge_method | string (S256) | optional | Only S256 is used. Example: S256 |
machine_name | string | optional | Runner name shown on the prompt. Example: Studio Mac |
device_id | string | optional | Stable device UUID. It keeps one machine identity across re-pairing. |
previous_machine_id | string | optional | Machine ID from an earlier pairing of the same device. |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/html | Html | The authorization prompt |
| 303 | — | — | Not signed in. Redirected to /login with return_to. |
| 400 | text/html | Html | Invalid callback, state, PKCE challenge, or device identity |
Example request
curl "https://rivetplane.com/authorize?redirect_uri=http%3A%2F%2F127.0.0.1%3A45678%2Fcallback&state=b7f1c2&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&machine_name=Studio%20Mac"
Example response
<!doctype html> ... Authorize local client ...
Claims a stable machine identity for the device, stores a one-time authorization code, and redirects to the loopback callback with code and state.
Authentication: Browser session cookies plus a CSRF form field.
Request body (application/x-www-form-urlencoded)
The prompt form fields.
| Field | Type | Requirement | Description |
|---|---|---|---|
csrf | string | required | CSRF token from the prompt. |
redirect_uri | string | required | Loopback callback. |
state | string | required | Client state. |
code_challenge | string | required | PKCE S256 challenge. |
machine_name | string | optional | Runner name. |
device_id | string | optional | Stable device UUID. |
previous_machine_id | string | optional | Earlier machine ID of the same device. |
{
"csrf": "cs_5f21c8",
"redirect_uri": "http://127.0.0.1:45678/callback",
"state": "b7f1c2",
"code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
"machine_name": "Studio Mac"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 303 | — | — | Redirected to the loopback callback with code and state |
| 400 | text/html | Html | Invalid CSRF token, callback, state, or PKCE challenge |
Example request
curl -X POST "https://rivetplane.com/authorize" \ -b "hcp_access=...; hcp_refresh=...; hcp_csrf=cs_5f21c8" \ --data-urlencode "csrf=cs_5f21c8" \ --data-urlencode "redirect_uri=http://127.0.0.1:45678/callback" \ --data-urlencode "state=b7f1c2" \ --data-urlencode "code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
Example response
HTTP/1.1 303 See Other location: http://127.0.0.1:45678/callback?code=1kQ7yq3f...&state=b7f1c2
Shows a CSRF-protected code entry form. The raw user code stays out of the URL and access logs. An anonymous user is sent to sign in.
Authentication: Browser session cookies plus a CSRF form field.
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/html | Html | Code entry page |
| 303 | — | — | Sign in before review |
Example request
curl "https://rivetplane.com/device"
Example response
<!doctype html> ... Pair a controller ...
Looks up the short code from a form body, then shows the controller name and exact minimum scopes.
Authentication: Browser session cookies plus a CSRF form field.
Request body (application/x-www-form-urlencoded)
CSRF token and short code.
| Field | Type | Requirement | Description |
|---|---|---|---|
csrf | string | required | CSRF token. |
user_code | string | required | Short pairing code. |
{
"csrf": "cs_5f21c8",
"user_code": "ABCD-EFGH"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/html | Html | Pairing review page |
| 403 | text/html | Html | Session or CSRF failure |
| 404 | text/html | Html | Unknown code |
| 409 | text/html | Html | Expired or completed request |
Example request
curl -X POST "https://rivetplane.com/device/review" -b "hcp_access=...; hcp_refresh=...; hcp_csrf=cs_5f21c8" --data-urlencode "csrf=cs_5f21c8" --data-urlencode "user_code=ABCD-EFGH"
Example response
<!doctype html> ... Approve controller ...
Applies one CSRF-protected decision to a pending, unexpired user code.
Authentication: Browser session cookies plus a CSRF form field.
Request body (application/x-www-form-urlencoded)
CSRF token, user code, and decision.
| Field | Type | Requirement | Description |
|---|---|---|---|
csrf | string | required | CSRF token. |
user_code | string | required | Short pairing code. |
decision | string (approved · denied) | required |
{
"csrf": "cs_5f21c8",
"user_code": "ABCD-EFGH",
"decision": "approved"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/html | Html | Decision confirmation |
| 400 | text/html | Html | Invalid decision or code |
| 403 | text/html | Html | Session or CSRF failure |
| 409 | text/html | Html | Grant is not pending |
Example request
curl -X POST "https://rivetplane.com/device" -b "hcp_access=...; hcp_refresh=...; hcp_csrf=cs_5f21c8" --data-urlencode "csrf=cs_5f21c8" --data-urlencode "user_code=ABCD-EFGH" --data-urlencode "decision=approved"
Example response
<!doctype html> ... Controller approved ...
Harnesses
Reports what every connected harness can do. Read it before you create a session or send a command. Adapter reports are authoritative. A discovery-only adapter can report transcripts but reject messages, interrupts, approvals, questions, or session creation.
Authentication: Bearer token in the Authorization header. Scope: read.
Query parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
machine | string | optional | Optional machine ID filter. Example: runner-01 |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | HarnessCapabilities[] | Capability reports visible to the token |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
Example request
curl "https://rivetplane.com/v1/harness-capabilities?machine=runner-01" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
[
{
"machine_id": "runner-01",
"harness_type": "codex",
"can_create_session": true,
"can_send_message": true,
"can_respond_to_approval": true,
"directories": [
"/workspace/app"
],
"models": [
{
"provider_id": "openai",
"model_id": "gpt-5.4",
"name": "GPT-5.4"
}
],
"reported_at": "2026-08-26T10:15:00Z"
}
]
Returns the allowed directories, the model roster, and the control operations that this harness accepts. Claude Code discovery is currently read-only when the adapter cannot use an official exact-ID control interface.
Authentication: Bearer token in the Authorization header. Scope: read.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
machine_id | string | required | The paired runner ID. Example: runner-01 |
harness | string | required | The harness type. Example: codex |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | HarnessCapabilities | The capability report |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 404 | application/json | Error | No capability report exists for this harness. |
Example request
curl "https://rivetplane.com/v1/machines/runner-01/harnesses/codex/capabilities" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
{
"machine_id": "runner-01",
"harness_type": "claude-code",
"can_create_session": false,
"can_send_message": false,
"can_interrupt": false,
"can_respond_to_approval": false,
"can_respond_to_question": false,
"directories": [],
"models": [],
"reported_at": "2026-08-26T10:15:00Z"
}
Relays a session-creation command to the runner. The directory and the model must both appear in the capability report. The new session ID arrives through the session_upsert event and through GET /v1/sessions. Session creation is available only through REST or MCP. The web UI has no Create session control. A 202 response confirms queue acceptance, not session completion.
Authentication: Bearer token in the Authorization header. Scope: write.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
machine_id | string | required | The paired runner ID. Example: runner-01 |
harness | string | required | The reported harness type. Example: codex |
Request body (application/json)
The directory and model to use.
| Field | Type | Requirement | Description |
|---|---|---|---|
cwd | string | required | Working directory. It must appear in the harness capability report. |
title | string | optional | Optional session title. |
model | object | required | Model to run. It must appear in the reported roster. |
{
"cwd": "/workspace/app",
"title": "Investigate CI",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
}
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 202 | application/json | CommandAccepted | The creation command was relayed to the runner |
| 400 | application/json | Error | The directory or model was not reported. |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 404 | application/json | Error | The target does not exist. |
| 409 | application/json | Error | The runner is offline or the target state changed. |
Example request
curl -X POST "https://rivetplane.com/v1/machines/runner-01/harnesses/codex/sessions" \
-H "Authorization: Bearer $RIVETPLANE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"cwd":"/workspace/app","title":"Investigate CI","model":{"provider_id":"openai","model_id":"gpt-5.4"}}'
Example response
{
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"accepted": true
}
Usage
Aggregates append-only usage samples. Late and out-of-order samples are included by timestamp. event_id makes replay and reconnect ingestion idempotent. Cost status and coverage prevent estimates or incomplete data from appearing as authoritative billing. Token fields are null when no matching source reported them. estimated cost is not an authoritative bill. coverage=partial means some matching samples had unavailable cost. Account-level quota can omit session_id.
Authentication: Bearer token in the Authorization header. Scope: read and usage:read.
Query parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
from | string | optional | Inclusive RFC 3339 start. Defaults to 00:00 UTC today. Example: 2026-08-26T00:00:00Z |
to | string | optional | Inclusive RFC 3339 end. Defaults to now. Example: 2026-08-26T23:59:59Z |
machine | string | optional | Filter by machine ID. |
harness | string | optional | Filter by harness type. Example: codex |
session | string | optional | Filter by session ID. |
provider | string | optional | Filter by provider. |
model | string | optional | Filter by model. |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | UsageReport | The aggregated usage report |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 400 | application/json | Error | from or to is invalid, or from is after to. |
Example request
curl "https://rivetplane.com/v1/usage?from=2026-08-26T00%3A00%3A00Z&to=2026-08-26T23%3A59%3A59Z&harness=codex" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
{
"range": {
"from": "2026-08-26T00:00:00Z",
"to": "2026-08-26T23:59:59Z"
},
"totals": {
"tokens": {
"input": 1200,
"output": 300,
"reasoning": null,
"cache_read": 450,
"cache_write": null,
"total": 1500
},
"cost": {
"status": "estimated",
"coverage": "partial",
"priced_samples": 2,
"unavailable_samples": 1,
"amount": 0.08,
"currency": "USD"
}
},
"breakdowns": {
"by_machine": [],
"by_harness": [
{
"key": "codex",
"tokens": {
"input": 1200,
"output": 300,
"reasoning": null,
"cache_read": 450,
"cache_write": null,
"total": 1500
},
"cost": {
"status": "estimated",
"coverage": "partial",
"priced_samples": 2,
"unavailable_samples": 1,
"amount": 0.08,
"currency": "USD"
}
}
],
"by_session": [],
"by_provider": [],
"by_model": []
},
"context": [
{
"machine_id": "runner-01",
"session_id": "runner-01/codex/ses_01",
"harness": "codex",
"provider": "openai",
"model": "gpt-5",
"timestamp": "2026-08-26T10:15:00Z",
"window_size": 200000,
"used_tokens": 1500
}
],
"quota": [
{
"machine_id": "runner-01",
"harness": "codex",
"provider": "openai",
"timestamp": "2026-08-26T10:15:00Z",
"windows": [
{
"name": "five_hour",
"used_percent": 35,
"resets_at": "2026-08-26T15:00:00Z"
}
]
}
],
"samples_count": 3
}
Sessions
Lists sessions across the fleet. Filter for triage, for example status=waiting_approval to find every session that waits on a human.
Authentication: Bearer token in the Authorization header. Scope: read.
Query parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
machine | string | optional | Filter by machine ID. Example: runner-01 |
harness | string | optional | Filter by harness type. Example: codex |
status | string (running · waiting_input · waiting_approval · completed · error) | optional | Filter by session status. Example: waiting_approval |
cwd | string | optional | Filter by working directory. Example: /workspace/app |
before | string | optional | Return sessions older than this ISO timestamp. Example: 2026-08-26T10:15:00Z |
limit | integer | optional | Return 1–1,000 sessions. Example: 20 |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | Session[] | Sessions visible to the token |
| 400 | application/json | Error | before must be a valid timestamp, or limit must be an integer from 1 to 1000 |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
Example request
curl "https://rivetplane.com/v1/sessions?status=waiting_approval&limit=20" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
[
{
"id": "runner-01/opencode/ses_01",
"machine_id": "runner-01",
"harness_type": "opencode",
"cwd": "/workspace/app",
"title": "Repair deployment workflow",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
},
"agent": "build",
"read_only": false,
"status": "waiting_approval",
"created_at": "2026-08-26T09:58:00Z",
"last_activity_at": "2026-08-26T10:15:00Z",
"pending": {
"type": "approval",
"id": "per_01",
"session_id": "runner-01/opencode/ses_01",
"tool_name": "shell",
"tool_input_summary": "bun run migrate",
"requested_at": "2026-08-26T10:15:00Z"
}
}
]
Returns the harness type, directory, status, timestamps, and the active pending interaction.
Authentication: Bearer token in the Authorization header. Scope: read.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
session_id | string | required | A percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01 |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | Session | The session |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 404 | application/json | Error | The session does not exist. |
Example request
curl "https://rivetplane.com/v1/sessions/runner-01%2Fcodex%2Fses_01" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
{
"id": "runner-01/codex/ses_01",
"machine_id": "runner-01",
"harness_type": "codex",
"cwd": "/workspace/app",
"title": "Repair deployment workflow",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
},
"agent": "default",
"read_only": false,
"metadata": {
"source": "app-server"
},
"status": "running",
"created_at": "2026-08-26T09:58:00Z",
"last_activity_at": "2026-08-26T10:15:00Z",
"pending": null
}
Returns retained transcript events in ascending sequence order. Follow next_cursor until it is null, then switch to the SSE stream for live events.
Authentication: Bearer token in the Authorization header. Scope: read.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
session_id | string | required | A percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01 |
Query parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
cursor | string | optional | Continue from a prior page. Example: NDI |
since | string | optional | Return events at or after an ISO timestamp. Example: 2026-08-26T10:15:00Z |
limit | integer | optional | Return 1–1,000 events. Example: 100 |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | TranscriptPage | A transcript page |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 400 | application/json | Error | The cursor or limit is invalid. |
| 404 | application/json | Error | The session does not exist. |
Example request
curl "https://rivetplane.com/v1/sessions/runner-01%2Fcodex%2Fses_01/transcript?limit=100" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
{
"events": [
{
"id": "evt_01",
"session_id": "runner-01/codex/ses_01",
"seq": 42,
"ts": "2026-08-26T10:15:00Z",
"type": "agent_message",
"payload": {
"text": "The tests pass."
}
}
],
"next_cursor": null
}
Opens a text/event-stream connection. The server writes a ': connected' comment, then one data frame per new transcript event. Read the paged transcript first, then stream, so no event is missed. The stream sends text/event-stream data records and closes when the client disconnects.
Authentication: Bearer token in the Authorization header. Scope: read.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
session_id | string | required | A percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01 |
Headers
| Name | Type | Requirement | Description |
|---|---|---|---|
Authorization | string | required | Bearer API token. Example: Bearer $RIVETPLANE_TOKEN |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/event-stream | TranscriptEvent | An open Server-Sent Events stream of TranscriptEvent values |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 404 | application/json | Error | The session does not exist. |
Example request
curl "https://rivetplane.com/v1/sessions/runner-01%2Fcodex%2Fses_01/transcript/stream" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN" \ --no-buffer
Example response
: connected
data: {"id":"evt_01","session_id":"runner-01/codex/ses_01","seq":42,"ts":"2026-08-26T10:15:00Z","type":"agent_message","payload":{"text":"The tests pass."}}
Returns the interaction the session waits on, or null. Always read this immediately before you respond, and reuse the exact ID.
Authentication: Bearer token in the Authorization header. Scope: read.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
session_id | string | required | A percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01 |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | PendingResponse | The active interaction, or null |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 404 | application/json | Error | The session does not exist. |
Example request
curl "https://rivetplane.com/v1/sessions/runner-01%2Fcodex%2Fses_01/pending" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
{
"pending": {
"type": "approval",
"id": "per_01",
"session_id": "runner-01/codex/ses_01",
"tool_name": "shell",
"tool_input_summary": "bun run migrate",
"requested_at": "2026-08-26T10:15:00Z"
}
}
Relays a user message into a live session. The harness reply arrives as transcript events, not in this response. Read-only adapters reject this command. Check the runner capability report first.
Authentication: Bearer token in the Authorization header. Scope: write.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
session_id | string | required | A percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01 |
Request body (application/json)
The message to relay.
| Field | Type | Requirement | Description |
|---|---|---|---|
text | string | required | Message text. It must not be empty or only whitespace. (min length 1) |
{
"text": "Continue with the migration plan."
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 202 | application/json | CommandAccepted | The message command was relayed to the runner |
| 400 | application/json | Error | text must not be empty |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 404 | application/json | Error | The target does not exist. |
| 409 | application/json | Error | The runner is offline or the target state changed. |
Example request
curl -X POST "https://rivetplane.com/v1/sessions/runner-01%2Fcodex%2Fses_01/messages" \
-H "Authorization: Bearer $RIVETPLANE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"text":"Continue with the migration plan."}'
Example response
{
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"accepted": true
}
Answers one approval or question. The pending_id must equal the ID currently returned by the pending read, so a new interaction cannot receive an old decision. A stale pending_id returns 409. Rivetplane never approves whatever happens to be pending. Read-only adapters reject this command.
Authentication: Bearer token in the Authorization header. Scope: write and pending:respond.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
session_id | string | required | A percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01 |
Request body (application/json)
The exact interaction ID and the decision.
| Field | Type | Requirement | Description |
|---|---|---|---|
pending_id | string | required | Exact ID of the active interaction. A stale ID returns 409. |
response | string | required | approve, deny, or the free text answer to a question. |
scope | ApprovalScope | optional | How long the harness keeps an approval decision. |
{
"pending_id": "per_01",
"response": "approve",
"scope": "once"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 202 | application/json | CommandAccepted | The response command was relayed to the runner |
| 400 | application/json | Error | response is required |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 404 | application/json | Error | The target does not exist. |
| 409 | application/json | Error | The runner is offline or the target state changed. |
Example request
curl -X POST "https://rivetplane.com/v1/sessions/runner-01%2Fcodex%2Fses_01/pending/respond" \
-H "Authorization: Bearer $RIVETPLANE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"pending_id":"per_01","response":"approve","scope":"once"}'
Example response
{
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"accepted": true
}
Relays an ACP cancel to the harness. A capable harness can treat an interrupt as a no-op when the session is idle. Read-only adapters reject this command.
Authentication: Bearer token in the Authorization header. Scope: write.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
session_id | string | required | A percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01 |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 202 | application/json | CommandAccepted | The interrupt command was relayed to the runner |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 404 | application/json | Error | The target does not exist. |
| 409 | application/json | Error | The runner is offline or the target state changed. |
Example request
curl -X POST "https://rivetplane.com/v1/sessions/runner-01%2Fcodex%2Fses_01/interrupt" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
{
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"accepted": true
}
Pending
Returns one fleet-wide inbox. By default it excludes offline runners and read-only adapters. Use include_non_actionable=true for diagnostics. The server checks the current machine state, adapter capability report, and pending interaction expiry. The default response contains only actionable items; expired native response windows are returned as read-only when include_non_actionable=true.
Authentication: Bearer token in the Authorization header. Scope: read.
Query parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
include_non_actionable | boolean | optional | Set true to include read-only, expired, and offline items with a reason. Example: false |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | PendingListItem[] | Pending items visible to the token |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
Example request
curl "https://rivetplane.com/v1/pending" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN"
Example response
[
{
"pending": {
"type": "approval",
"id": "per_01",
"session_id": "runner-01/codex/ses_01",
"tool_name": "shell",
"tool_input_summary": "bun run migrate",
"requested_at": "2026-08-26T10:15:00Z"
},
"session_id": "runner-01/codex/ses_01",
"machine_id": "runner-01",
"harness_type": "codex",
"cwd": "/workspace/app",
"title": "Repair deployment workflow",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
},
"agent": "default",
"read_only": false,
"actionable": true
}
]
Finds the interaction by exact ID, checks adapter support and current state again, sends the response, and waits for the runner result. The server re-fetches the exact pending ID before it sends the response. HTTP 200 means the runner reported success. A runner rejection returns 409.
Authentication: Bearer token in the Authorization header. Scope: write and pending:respond.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
pending_id | string | required | Exact ID from GET /v1/pending. |
Request body (application/json)
The decision or answer.
| Field | Type | Requirement | Description |
|---|---|---|---|
response | string | required | approve, deny, or the free text answer to a question. |
scope | ApprovalScope | optional | How long the harness keeps an approval decision. |
{
"response": "approve",
"scope": "once"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | CommandCompleted | The runner applied the response |
| 400 | application/json | Error | response is required |
| 401 | application/json | Error | The bearer token is missing, expired, or invalid. |
| 403 | application/json | Error | The token cannot access this account, machine, or session. |
| 404 | application/json | Error | The target does not exist. |
| 409 | application/json | Error | The runner is offline or the target state changed. |
| 504 | application/json | Error | The runner did not report a result before the timeout. |
Example request
curl -X POST "https://rivetplane.com/v1/pending/pending_id/respond" \
-H "Authorization: Bearer $RIVETPLANE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"response":"approve","scope":"once"}'
Example response
{
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"completed": true
}
MCP
One JSON-RPC 2.0 endpoint for initialize, ping, tools/list, tools/call, resources/list, resources/templates/list, and resources/read. Notifications receive 202 with an empty body. Tools that act on a session need the write scope.
Authentication: Bearer token in the Authorization header. Scope: read.
Request body (application/json)
A JSON-RPC 2.0 request.
| Field | Type | Requirement | Description |
|---|---|---|---|
jsonrpc | "2.0" | required | |
id | string | integer | null | optional | Omit it only for notifications. |
method | string | required | initialize, ping, tools/list, tools/call, resources/list, resources/templates/list, resources/read, or a notifications/* method. |
params | object | optional | Method parameters. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | JsonRpcResponse | A JSON-RPC 2.0 result |
| 202 | — | — | A notification was accepted. The body is empty. |
| 400 | application/json | Error | Tool name is required, or a required argument is missing |
| 401 | application/json | Error | Missing or invalid bearer token |
| 403 | application/json | Error | Token cannot access this account, machine, or session |
| 404 | application/json | Error | Unknown MCP method, tool, or resource |
| 409 | application/json | Error | Machine is offline, or the pending interaction changed |
Example request
curl -X POST "https://rivetplane.com/mcp" \
-H "Authorization: Bearer $RIVETPLANE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Example response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "list_sessions",
"description": "List visible sessions with optional filters.",
"inputSchema": {
"type": "object",
"properties": {
"filter": {
"type": "object"
}
}
}
}
]
}
}
Billing
Stripe calls this public endpoint. The server verifies the signature against the unparsed body before it stores customer, subscription, plan, or status changes.
Authentication: Stripe-Signature header verified against the raw body.
Headers
| Name | Type | Requirement | Description |
|---|---|---|---|
Stripe-Signature | string | required | Stripe signature over the raw request body. |
Request body (application/json)
The raw Stripe event.
{
"id": "evt_1PxTest",
"type": "customer.subscription.updated",
"data": {
"object": {
"id": "sub_test"
}
}
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | application/json | StripeWebhookAck | The event was accepted |
| 400 | application/json | Error | Stripe webhook signature or payload is not valid |
| 503 | application/json | Error | Stripe Billing is not configured |
Example request
curl -X POST "https://rivetplane.com/v1/billing/webhook" \
-H "Content-Type: application/json" \
-d '{"id":"evt_1PxTest","type":"customer.subscription.updated"}' \
-H "Stripe-Signature: t=1756000000,v1=..."
Example response
{
"received": true
}
Creates a Stripe Checkout session for the signed-in account and redirects to it. Self-hosted deployments without Stripe configuration return 503.
Authentication: Browser session cookies plus a CSRF form field.
Request body (application/x-www-form-urlencoded)
Form fields: csrf and plan.
| Field | Type | Requirement | Description |
|---|---|---|---|
csrf | string | required | CSRF token from the dashboard. |
plan | string (pro · team) | optional | Plan to purchase. The default is pro. |
{
"csrf": "cs_5f21c8",
"plan": "pro"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 303 | — | — | Redirected to Stripe Checkout, or to /login when the visitor is anonymous |
| 403 | text/html | Html | CSRF mismatch |
| 502 | text/html | Html | Stripe rejected the request |
| 503 | text/html | Html | Billing is not configured |
Example request
curl -X POST "https://rivetplane.com/billing/checkout" \ -b "hcp_access=...; hcp_refresh=...; hcp_csrf=cs_5f21c8" \ --data-urlencode "csrf=cs_5f21c8" \ --data-urlencode "plan=pro"
Example response
HTTP/1.1 303 See Other location: https://checkout.stripe.com/c/pay/cs_test_...
Creates a Stripe billing portal session for the signed-in account so the user can change the plan, the payment method, or read invoices.
Authentication: Browser session cookies plus a CSRF form field.
Request body (application/x-www-form-urlencoded)
Form field: csrf.
| Field | Type | Requirement | Description |
|---|---|---|---|
csrf | string | required | CSRF token from the dashboard. |
{
"csrf": "cs_5f21c8"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 303 | — | — | Redirected to the Stripe customer portal, or to /login when the visitor is anonymous |
| 403 | text/html | Html | CSRF mismatch |
| 502 | text/html | Html | Stripe rejected the request |
| 503 | text/html | Html | Billing is not configured |
Example request
curl -X POST "https://rivetplane.com/billing/portal" \ -b "hcp_access=...; hcp_refresh=...; hcp_csrf=cs_5f21c8" \ --data-urlencode "csrf=cs_5f21c8"
Example response
HTTP/1.1 303 See Other location: https://billing.stripe.com/p/session/test_...
Browser
Returns the product page for anonymous visitors and the fleet dashboard for a signed-in browser session. The dashboard issues API tokens and shows runner state.
Authentication: Browser session cookies plus a CSRF form field.
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/html | Html | The landing page, or the dashboard for a signed-in session |
Example request
curl "https://rivetplane.com/"
Example response
<!doctype html> ... Any harness. One plane. ...
Renders the sign-in form. return_to keeps a same-origin destination, for example an /authorize request from the local client.
Authentication: None. The endpoint is public.
Query parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
return_to | string | optional | Same-origin path to open after sign-in. Example: /docs |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/html | Html | The sign-in form |
Example request
curl "https://rivetplane.com/login"
Example response
<!doctype html> ... auth-page ...
Renders the account creation form. Supabase Auth owns the account record and the confirmation email.
Authentication: None. The endpoint is public.
Query parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
return_to | string | optional | Same-origin path to open after sign-up. Example: / |
Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 200 | text/html | Html | The sign-up form |
Example request
curl "https://rivetplane.com/signup"
Example response
<!doctype html> ... auth-page ...
Form submission. On success the server sets the access, refresh, and CSRF cookies and redirects to return_to.
Authentication: None. The endpoint is public.
Request body (application/x-www-form-urlencoded)
Form fields: email, password, and return_to.
| Field | Type | Requirement | Description |
|---|---|---|---|
email | string | required | Account email. |
password | string | required | Password with at least 12 characters. |
return_to | string | optional | Same-origin path to open after sign-in. |
{
"email": "dev@example.com",
"password": "correct horse battery staple",
"return_to": "/"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 303 | — | — | Signed in. The response sets session cookies and redirects. |
| 400 | text/html | Html | Invalid email or short password |
| 401 | text/html | Html | Authentication failed |
Example request
curl -X POST "https://rivetplane.com/auth/login" \ --data-urlencode "email=dev@example.com" \ --data-urlencode "password=correct horse battery staple" \ --data-urlencode "return_to=/"
Example response
HTTP/1.1 303 See Other location: / set-cookie: hcp_access=...; HttpOnly; SameSite=Lax
Form submission. When Supabase requires email confirmation the server returns 202 with a confirmation page instead of a session.
Authentication: None. The endpoint is public.
Request body (application/x-www-form-urlencoded)
Form fields: email, password, and return_to.
| Field | Type | Requirement | Description |
|---|---|---|---|
email | string | required | Account email. |
password | string | required | Password with at least 12 characters. |
return_to | string | optional | Same-origin path to open after sign-up. |
{
"email": "dev@example.com",
"password": "correct horse battery staple",
"return_to": "/"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 303 | — | — | Account created with an active session |
| 202 | text/html | Html | Account created. A confirmation email was sent. |
| 400 | text/html | Html | Invalid email or short password |
| 409 | text/html | Html | The account already exists |
Example request
curl -X POST "https://rivetplane.com/auth/signup" \ --data-urlencode "email=dev@example.com" \ --data-urlencode "password=correct horse battery staple"
Example response
HTTP/1.1 303 See Other location: / set-cookie: hcp_access=...; HttpOnly; SameSite=Lax
Revokes the Supabase session and clears the cookies. The csrf field must equal the hcp_csrf cookie.
Authentication: Browser session cookies plus a CSRF form field.
Request body (application/x-www-form-urlencoded)
Form field: csrf.
| Field | Type | Requirement | Description |
|---|---|---|---|
csrf | string | required | CSRF token from the page form. |
{
"csrf": "cs_5f21c8"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 303 | — | — | Signed out and redirected to /login |
| 403 | text/html | Html | Missing session or CSRF mismatch |
Example request
curl -X POST "https://rivetplane.com/logout" \ -b "hcp_access=...; hcp_refresh=...; hcp_csrf=cs_5f21c8" \ --data-urlencode "csrf=cs_5f21c8"
Example response
HTTP/1.1 303 See Other location: /login
Creates an account API token with read and write scope. The secret appears once in the response page. The server keeps only its hash.
Authentication: Browser session cookies plus a CSRF form field.
Request body (application/x-www-form-urlencoded)
Form field: csrf.
| Field | Type | Requirement | Description |
|---|---|---|---|
csrf | string | required | CSRF token from the dashboard form. |
{
"csrf": "cs_5f21c8"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 201 | text/html | Html | The token page. The secret is shown once. |
| 303 | — | — | Not signed in. Redirected to /login. |
| 403 | text/html | Html | CSRF mismatch |
Example request
curl -X POST "https://rivetplane.com/api-tokens" \ -b "hcp_access=...; hcp_refresh=...; hcp_csrf=cs_5f21c8" \ --data-urlencode "csrf=cs_5f21c8"
Example response
<!doctype html> ... <pre>Yb2m9Qs4Tt7Vx1Zc5Hg8Kj0Lp3Rn6Wd</pre> ...
The dashboard form equivalent of POST /v1/machines/{machine_id}/retire. It writes the same audit record and closes the relay connection.
Authentication: Browser session cookies plus a CSRF form field.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
machine_id | string | required | Machine ID from GET /v1/machines. Example: runner-01 |
Request body (application/x-www-form-urlencoded)
Form field: csrf.
| Field | Type | Requirement | Description |
|---|---|---|---|
csrf | string | required | CSRF token from the runner card. |
{
"csrf": "cs_5f21c8"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 303 | — | — | The runner is retired. Redirected to the dashboard. |
| 403 | text/html | Html | Missing session or CSRF mismatch |
| 404 | text/html | Html | Runner not found |
Example request
curl -X POST "https://rivetplane.com/machines/runner-01/retire" \ -b "hcp_access=...; hcp_refresh=...; hcp_csrf=cs_5f21c8" \ --data-urlencode "csrf=cs_5f21c8"
Example response
HTTP/1.1 303 See Other location: /
CSRF-protected dashboard action. It revokes the controller and all its active bearer tokens.
Authentication: Browser session cookies plus a CSRF form field.
Path parameters
| Name | Type | Requirement | Description |
|---|---|---|---|
device_id | string | required | Consumer device record ID. |
Request body (application/x-www-form-urlencoded)
Form field: csrf.
| Field | Type | Requirement | Description |
|---|---|---|---|
csrf | string | required | CSRF token from the dashboard. |
{
"csrf": "cs_5f21c8"
}Responses
| Status | Content type | Body | Meaning |
|---|---|---|---|
| 303 | — | — | Controller revoked and dashboard opened |
| 403 | text/html | Html | Session or CSRF failure |
| 404 | text/html | Html | Controller not found |
Example request
curl -X POST "https://rivetplane.com/consumer-devices/6c2fd71b-e082-4aca-b8ea-f52704ccd501/revoke" -b "hcp_access=...; hcp_refresh=...; hcp_csrf=cs_5f21c8" --data-urlencode "csrf=cs_5f21c8"
Example response
HTTP/1.1 303 See Other location: /
Streaming
Two live surfaces exist. Server-Sent Events tail one transcript. A WebSocket carries every event the token can see. Read the paged transcript first and then open the stream, so no event is lost between the page and the tail.
Transcript SSE
Response headers: content-type: text/event-stream, cache-control: no-cache, connection: keep-alive. The server writes a : connected comment, then one data: frame per transcript event. Each frame holds one complete TranscriptEvent.
curl "https://rivetplane.com/v1/sessions/runner-01%2Fcodex%2Fses_01/transcript/stream" \ -H "Authorization: Bearer $RIVETPLANE_TOKEN" \ --no-buffer
: connected
data: {"id":"evt_01","session_id":"runner-01/codex/ses_01","seq":42,"ts":"2026-08-26T10:15:00Z","type":"agent_message","payload":{"text":"The tests pass."}}
One WebSocket that carries every event the token can see: runner presence, session upserts and removals, harness capability reports, transcript events, and command activity. This is the feed for 'show me everything that waits on me'.
Authentication: Authorization: Bearer <API_TOKEN>. Browsers that cannot set headers may send the subprotocol bearer.<base64url-token> instead. Principal: API token, or a machine token that receives only its own events
Handshake
GET /v1/events/stream HTTP/1.1 Host: rivetplane.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Version: 13 Authorization: Bearer $RIVETPLANE_TOKEN
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade
Frames
| Frame | Direction | Meaning |
|---|---|---|
session_upsert | server → client | A session was created or changed. data is the complete Session. |
transcript_event | server → client | A new transcript event. data is the TranscriptEvent. |
usage_sample | server → client | Privacy-safe usage metadata was stored. data never contains prompt or file content. |
machine_online | server → client | A runner connected. data is the Machine. |
machine_offline | server → client | A runner disconnected. data is empty. |
session_removed | server → client | The runner no longer reports the session. |
harness_capabilities | server → client | A harness reported new directories or models. |
command_sent | server → client | The control plane relayed a command. Correlate with the 202 command_id. |
command_result | server → client | The runner reported the outcome of a relayed command. |
session_upsert
| Field | Type | Requirement | Description |
|---|---|---|---|
type | string (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result) | required | Event kind. |
ts | string | required | Time the server published the event. (format date-time) |
machine_id | string | optional | Machine the event belongs to. |
session_id | string | optional | Session the event belongs to, when the event is session-scoped. |
data | object | optional | Machine, Session, TranscriptEvent, HarnessCapabilities, or command payload, per type. |
{
"type": "session_upsert",
"ts": "2026-08-26T10:15:00Z",
"machine_id": "runner-01",
"session_id": "runner-01/codex/ses_01",
"data": {
"id": "runner-01/codex/ses_01",
"machine_id": "runner-01",
"harness_type": "codex",
"cwd": "/workspace/app",
"title": "Repair deployment workflow",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
},
"agent": "default",
"read_only": false,
"metadata": {
"source": "app-server"
},
"status": "running",
"created_at": "2026-08-26T09:58:00Z",
"last_activity_at": "2026-08-26T10:15:00Z",
"pending": null
}
}transcript_event
| Field | Type | Requirement | Description |
|---|---|---|---|
type | string (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result) | required | Event kind. |
ts | string | required | Time the server published the event. (format date-time) |
machine_id | string | optional | Machine the event belongs to. |
session_id | string | optional | Session the event belongs to, when the event is session-scoped. |
data | object | optional | Machine, Session, TranscriptEvent, HarnessCapabilities, or command payload, per type. |
{
"type": "transcript_event",
"ts": "2026-08-26T10:15:00Z",
"machine_id": "runner-01",
"session_id": "runner-01/codex/ses_01",
"data": {
"id": "evt_01",
"session_id": "runner-01/codex/ses_01",
"seq": 42,
"ts": "2026-08-26T10:15:00Z",
"type": "agent_message",
"payload": {
"text": "The tests pass."
}
}
}usage_sample
| Field | Type | Requirement | Description |
|---|---|---|---|
type | string (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result) | required | Event kind. |
ts | string | required | Time the server published the event. (format date-time) |
machine_id | string | optional | Machine the event belongs to. |
session_id | string | optional | Session the event belongs to, when the event is session-scoped. |
data | object | optional | Machine, Session, TranscriptEvent, HarnessCapabilities, or command payload, per type. |
{
"type": "usage_sample",
"ts": "2026-08-26T10:15:00Z",
"machine_id": "runner-01",
"session_id": "runner-01/codex/ses_01",
"data": {
"event_id": "usage-01",
"harness": "codex",
"source": "codex.app_server",
"tokens": {
"total": 1500
},
"cost": {
"status": "estimated",
"amount": 0.08,
"currency": "USD"
}
}
}machine_online
| Field | Type | Requirement | Description |
|---|---|---|---|
type | string (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result) | required | Event kind. |
ts | string | required | Time the server published the event. (format date-time) |
machine_id | string | optional | Machine the event belongs to. |
session_id | string | optional | Session the event belongs to, when the event is session-scoped. |
data | object | optional | Machine, Session, TranscriptEvent, HarnessCapabilities, or command payload, per type. |
{
"type": "machine_online",
"ts": "2026-08-26T10:15:00Z",
"machine_id": "runner-01",
"data": {
"id": "runner-01",
"name": "Studio Mac",
"owner_account_id": "account-01",
"last_seen_at": "2026-08-26T10:15:00Z",
"status": "online",
"harnesses": [
"codex",
"opencode"
]
}
}machine_offline
| Field | Type | Requirement | Description |
|---|---|---|---|
type | string (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result) | required | Event kind. |
ts | string | required | Time the server published the event. (format date-time) |
machine_id | string | optional | Machine the event belongs to. |
session_id | string | optional | Session the event belongs to, when the event is session-scoped. |
data | object | optional | Machine, Session, TranscriptEvent, HarnessCapabilities, or command payload, per type. |
{
"type": "machine_offline",
"ts": "2026-08-26T10:15:00Z",
"machine_id": "runner-01",
"data": {}
}session_removed
| Field | Type | Requirement | Description |
|---|---|---|---|
type | string (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result) | required | Event kind. |
ts | string | required | Time the server published the event. (format date-time) |
machine_id | string | optional | Machine the event belongs to. |
session_id | string | optional | Session the event belongs to, when the event is session-scoped. |
data | object | optional | Machine, Session, TranscriptEvent, HarnessCapabilities, or command payload, per type. |
{
"type": "session_removed",
"ts": "2026-08-26T10:15:00Z",
"machine_id": "runner-01",
"session_id": "runner-01/codex/ses_01",
"data": {}
}harness_capabilities
| Field | Type | Requirement | Description |
|---|---|---|---|
type | string (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result) | required | Event kind. |
ts | string | required | Time the server published the event. (format date-time) |
machine_id | string | optional | Machine the event belongs to. |
session_id | string | optional | Session the event belongs to, when the event is session-scoped. |
data | object | optional | Machine, Session, TranscriptEvent, HarnessCapabilities, or command payload, per type. |
{
"type": "harness_capabilities",
"ts": "2026-08-26T10:15:00Z",
"machine_id": "runner-01",
"data": {
"machine_id": "runner-01",
"harness_type": "codex",
"can_create_session": true,
"can_send_message": true,
"can_respond_to_approval": true,
"directories": [
"/workspace/app"
],
"models": [
{
"provider_id": "openai",
"model_id": "gpt-5.4",
"name": "GPT-5.4"
}
],
"reported_at": "2026-08-26T10:15:00Z"
}
}command_sent
| Field | Type | Requirement | Description |
|---|---|---|---|
type | string (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result) | required | Event kind. |
ts | string | required | Time the server published the event. (format date-time) |
machine_id | string | optional | Machine the event belongs to. |
session_id | string | optional | Session the event belongs to, when the event is session-scoped. |
data | object | optional | Machine, Session, TranscriptEvent, HarnessCapabilities, or command payload, per type. |
{
"type": "command_sent",
"ts": "2026-08-26T10:15:00Z",
"machine_id": "runner-01",
"session_id": "runner-01/codex/ses_01",
"data": {
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"command_type": "command.send_message",
"actor": "api:5c1f"
}
}command_result
| Field | Type | Requirement | Description |
|---|---|---|---|
type | string (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result) | required | Event kind. |
ts | string | required | Time the server published the event. (format date-time) |
machine_id | string | optional | Machine the event belongs to. |
session_id | string | optional | Session the event belongs to, when the event is session-scoped. |
data | object | optional | Machine, Session, TranscriptEvent, HarnessCapabilities, or command payload, per type. |
{
"type": "command_result",
"ts": "2026-08-26T10:15:00Z",
"machine_id": "runner-01",
"data": {
"type": "command.result",
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"ok": true,
"result": {
"session_id": "runner-01/codex/ses_01"
}
}
}
Close codes
| Code | Reason |
|---|---|
| 1008 | The token is not valid for this channel. |
| 1001 | The server is stopping. |
The outbound connection that a local runner keeps open. The runner publishes machine, capability, session, and transcript state; the server pushes commands back. The server never dials into a developer machine. Frames are applied in order, so a transcript event cannot overtake its session.
Authentication: Authorization: Bearer <MACHINE_TOKEN> from POST /v1/auth/token. Principal: Machine token only
Handshake
GET /v1/relay HTTP/1.1 Host: rivetplane.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Version: 13 Authorization: Bearer $MACHINE_TOKEN
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade
Frames
| Frame | Direction | Meaning |
|---|---|---|
machine.hello | client → server | First frame. It registers the runner and replaces an older connection of the same machine. The server overwrites owner_account_id with the token account. |
machine.heartbeat | client → server | Keeps the runner marked online. |
harness.capabilities | client → server | Reports directories, models, and whether remote session creation is allowed. |
session.upsert | client → server | Creates or updates a session, including the pending interaction. |
transcript.append | client → server | Appends one transcript event. Repeating a sequence number is ignored, so reconnects are safe. |
usage.sample | client → server | Appends one privacy-safe usage sample. A stable event_id makes replay idempotent. Token values are normalized deltas; source_counter_mode records the native counter form. |
session.removed | client → server | The harness no longer has the session. |
command.result | client → server | Reports the outcome of a server command. It is republished on the event stream. |
relay.error | server → client | A frame was rejected. The connection stays open. |
command.send_message | server → client | Deliver a user message to the harness session. |
command.respond_to_pending | server → client | Answer the exact pending interaction. |
command.interrupt_session | server → client | Cancel the active turn. |
command.create_session | server → client | Start a new harness session in a reported directory with a reported model. |
machine.hello
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "machine.hello" | required | |
protocol_version | 1 | required | |
machine | Machine | required | One paired local runner. |
{
"type": "machine.hello",
"protocol_version": 1,
"machine": {
"id": "runner-01",
"name": "Studio Mac",
"owner_account_id": "account-01",
"last_seen_at": "2026-08-26T10:15:00Z",
"status": "online",
"harnesses": [
"codex",
"opencode"
]
}
}machine.heartbeat
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "machine.heartbeat" | required | |
machine_id | string | required | |
sent_at | string | required | Send time. (format date-time) |
{
"type": "machine.heartbeat",
"machine_id": "runner-01",
"sent_at": "2026-08-26T10:15:00Z"
}harness.capabilities
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "harness.capabilities" | required | |
capabilities | HarnessCapabilities | required | What one harness on one machine can do right now. |
{
"type": "harness.capabilities",
"capabilities": {
"machine_id": "runner-01",
"harness_type": "codex",
"can_create_session": true,
"can_send_message": true,
"can_respond_to_approval": true,
"directories": [
"/workspace/app"
],
"models": [
{
"provider_id": "openai",
"model_id": "gpt-5.4",
"name": "GPT-5.4"
}
],
"reported_at": "2026-08-26T10:15:00Z"
}
}session.upsert
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "session.upsert" | required | |
session | Session | required | One harness session on one machine. |
{
"type": "session.upsert",
"session": {
"id": "runner-01/codex/ses_01",
"machine_id": "runner-01",
"harness_type": "codex",
"cwd": "/workspace/app",
"title": "Repair deployment workflow",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
},
"agent": "default",
"read_only": false,
"metadata": {
"source": "app-server"
},
"status": "running",
"created_at": "2026-08-26T09:58:00Z",
"last_activity_at": "2026-08-26T10:15:00Z",
"pending": null
}
}transcript.append
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "transcript.append" | required | |
event | TranscriptEvent | required | One append-only transcript record. The payload shape follows the type. |
{
"type": "transcript.append",
"event": {
"id": "evt_01",
"session_id": "runner-01/codex/ses_01",
"seq": 42,
"ts": "2026-08-26T10:15:00Z",
"type": "agent_message",
"payload": {
"text": "The tests pass."
}
}
}usage.sample
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "usage.sample" | required | |
sample | object | required | UsageSample from @harness-cp/shared. It contains metadata only and no prompt, response, file, or tool payload. |
{
"type": "usage.sample",
"sample": {
"event_id": "usage-01",
"machine_id": "runner-01",
"session_id": "runner-01/codex/ses_01",
"timestamp": "2026-08-26T10:15:00Z",
"harness": "codex",
"source": "codex.app_server",
"provider": "openai",
"model": "gpt-5",
"source_counter_mode": "cumulative",
"tokens": {
"input": 1200,
"output": 300,
"reasoning": null,
"cache_read": 450,
"cache_write": null,
"total": 1500
},
"cost": {
"status": "estimated",
"amount": 0.08,
"currency": "USD"
}
}
}session.removed
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "session.removed" | required | |
session_id | string | required | |
removed_at | string | required | Removal time. (format date-time) |
{
"type": "session.removed",
"session_id": "runner-01/codex/ses_01",
"removed_at": "2026-08-26T10:15:00Z"
}command.result
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "command.result" | required | |
command_id | string | required | |
ok | boolean | required | |
error | string | optional | |
result | object | optional |
{
"type": "command.result",
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"ok": true,
"result": {
"session_id": "runner-01/codex/ses_01"
}
}relay.error
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "relay.error" | required | |
error | string | required |
{
"type": "relay.error",
"error": "Session does not belong to this machine"
}command.send_message
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "command.send_message" | required | |
command_id | string | required | |
session_id | string | required | |
text | string | required |
{
"type": "command.send_message",
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"session_id": "runner-01/codex/ses_01",
"text": "Continue with the migration plan."
}command.respond_to_pending
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "command.respond_to_pending" | required | |
command_id | string | required | |
session_id | string | required | |
pending_id | string | required | |
response | string | required | |
scope | ApprovalScope | optional | How long the harness keeps an approval decision. |
{
"type": "command.respond_to_pending",
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"session_id": "runner-01/codex/ses_01",
"pending_id": "per_01",
"response": "approve",
"scope": "once"
}command.interrupt_session
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "command.interrupt_session" | required | |
command_id | string | required | |
session_id | string | required |
{
"type": "command.interrupt_session",
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"session_id": "runner-01/codex/ses_01"
}command.create_session
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "command.create_session" | required | |
command_id | string | required | |
machine_id | string | required | |
harness_type | string | required | |
cwd | string | required | |
title | string | optional | |
model | object | required |
{
"type": "command.create_session",
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"machine_id": "runner-01",
"harness_type": "codex",
"cwd": "/workspace/app",
"title": "Investigate CI",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
}
}
Close codes
| Code | Reason |
|---|---|
| 1003 | A frame was not valid JSON. |
| 1008 | Machine token required, token mismatch, replaced by a newer connection, or the runner was retired. |
| 1001 | The server is stopping. |
MCP
Connect a Streamable HTTP MCP client to https://rivetplane.com/mcp. The endpoint speaks JSON-RPC 2.0, protocol version 2025-06-18, and uses the same bearer token as REST. Tools run the same authorized operations as the REST API. There is no private control path.
{
"mcpServers": {
"rivetplane": {
"url": "https://rivetplane.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_TOKEN"
}
}
}
}
Methods
Returns the protocol version and the advertised capabilities. Rivetplane serves tools and resources. The bearer token is required on this call, like on every other call.
Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "example-client",
"version": "1.0.0"
}
}
}Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {},
"resources": {}
},
"serverInfo": {
"name": "rivetplane",
"title": "Rivetplane",
"version": "0.1.0"
}
}
}A notification has no id. The server answers with HTTP 202 and an empty body. Every notifications/* method behaves the same way.
Request
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}Response
HTTP/1.1 202 Accepted (empty body)
Returns an empty result.
Request
{
"jsonrpc": "2.0",
"id": 2,
"method": "ping"
}Response
{
"jsonrpc": "2.0",
"id": 2,
"result": {}
}Returns every tool with its JSON Schema. The list is stable for a server version.
Request
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/list"
}Response
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"tools": [
{
"name": "get_pending",
"description": "Get the current approval or question.",
"inputSchema": {
"type": "object",
"properties": {
"session_id": {
"type": "string"
}
},
"required": [
"session_id"
]
}
},
{
"name": "…",
"description": "The other tools follow the same shape."
}
]
}
}Runs one tool. The result carries both a text block and structuredContent.result, so strict and lenient clients both work. A failed call returns an HTTP status with an Error body, not a JSON-RPC result.
Request
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "get_pending",
"arguments": {
"session_id": "runner-01/codex/ses_01"
}
}
}Response
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"type\": \"approval\",\n \"id\": \"per_01\",\n \"session_id\": \"runner-01/codex/ses_01\",\n \"tool_name\": \"shell\",\n \"tool_input_summary\": \"bun run migrate\",\n \"requested_at\": \"2026-08-26T10:15:00Z\"\n}"
}
],
"structuredContent": {
"result": {
"type": "approval",
"id": "per_01",
"session_id": "runner-01/codex/ses_01",
"tool_name": "shell",
"tool_input_summary": "bun run migrate",
"requested_at": "2026-08-26T10:15:00Z"
}
},
"isError": false
}
}Rivetplane exposes no fixed resources. It exposes one template, so the caller loads a transcript by session ID.
Request
{
"jsonrpc": "2.0",
"id": 5,
"method": "resources/list"
}Response
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"resources": [],
"resourceTemplates": [
{
"uriTemplate": "session://{id}/transcript",
"name": "Session transcript",
"mimeType": "application/json"
}
]
}
}Returns the transcript template only.
Request
{
"jsonrpc": "2.0",
"id": 6,
"method": "resources/templates/list"
}Response
{
"jsonrpc": "2.0",
"id": 6,
"result": {
"resourceTemplates": [
{
"uriTemplate": "session://{id}/transcript",
"name": "Session transcript",
"mimeType": "application/json"
}
]
}
}Loads the complete transcript as context instead of paging through tool calls. Percent-encode the session ID inside the URI.
Request
{
"jsonrpc": "2.0",
"id": 7,
"method": "resources/read",
"params": {
"uri": "session://runner-01%2Fcodex%2Fses_01/transcript"
}
}Response
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"contents": [
{
"uri": "session://runner-01%2Fcodex%2Fses_01/transcript",
"mimeType": "application/json",
"text": "[{\"id\":\"evt_01\",\"session_id\":\"runner-01/codex/ses_01\",\"seq\":42,\"ts\":\"2026-08-26T10:15:00Z\",\"type\":\"agent_message\",\"payload\":{\"text\":\"The tests pass.\"}}]"
}
]
}
}Tools
| Tool | Scope | Result | Same as REST |
|---|---|---|---|
list_sessions | read | Session[] | list_sessions |
get_session | read | Session | get_session |
send_message | read + write | CommandAccepted | send_message |
interrupt_session | read + write | CommandAccepted | interrupt_session |
create_session | read + write | CommandAccepted | create_session |
list_machines | read | Machine[] | list_machines |
list_harness_capabilities | read | HarnessCapabilities[] | list_harness_capabilities |
get_usage | read | UsageReport | get_usage |
get_pending | read | PendingInteraction | get_pending |
respond_to_pending | read + write | CommandAccepted | respond_pending |
list_pending | read | PendingListItem[] | list_actionable_pending |
respond_to_pending_by_id | read + write | CommandCompleted | respond_actionable_pending |
get_transcript | read | TranscriptPage | get_transcript |
list_sessions
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_sessions",
"arguments": {
"filter": {
"status": "waiting_approval"
}
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "[\n {\n \"id\": \"runner-01/opencode/ses_01\",\n \"machine_id\": \"runner-01\",\n \"harness_type\": \"opencode\",\n \"cwd\": \"/workspace/app\",\n \"title\": \"Repair deployment workflow\",\n \"model\": {\n \"provider_id\": \"openai\",\n \"model_id\": \"gpt-5.4\"\n },\n \"agent\": \"build\",\n \"read_only\": false,\n \"status\": \"waiting_approval\",\n \"created_at\": \"2026-08-26T09:58:00Z\",\n \"last_activity_at\": \"2026-08-26T10:15:00Z\",\n \"pending\": {\n \"type\": \"approval\",\n \"id\": \"per_01\",\n \"session_id\": \"runner-01/opencode/ses_01\",\n \"tool_name\": \"shell\",\n \"tool_input_summary\": \"bun run migrate\",\n \"requested_at\": \"2026-08-26T10:15:00Z\"\n }\n }\n]"
}
],
"structuredContent": {
"result": [
{
"id": "runner-01/opencode/ses_01",
"machine_id": "runner-01",
"harness_type": "opencode",
"cwd": "/workspace/app",
"title": "Repair deployment workflow",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
},
"agent": "build",
"read_only": false,
"status": "waiting_approval",
"created_at": "2026-08-26T09:58:00Z",
"last_activity_at": "2026-08-26T10:15:00Z",
"pending": {
"type": "approval",
"id": "per_01",
"session_id": "runner-01/opencode/ses_01",
"tool_name": "shell",
"tool_input_summary": "bun run migrate",
"requested_at": "2026-08-26T10:15:00Z"
}
}
]
},
"isError": false
}
}get_session
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_session",
"arguments": {
"session_id": "runner-01/codex/ses_01"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"id\": \"runner-01/codex/ses_01\",\n \"machine_id\": \"runner-01\",\n \"harness_type\": \"codex\",\n \"cwd\": \"/workspace/app\",\n \"title\": \"Repair deployment workflow\",\n \"model\": {\n \"provider_id\": \"openai\",\n \"model_id\": \"gpt-5.4\"\n },\n \"agent\": \"default\",\n \"read_only\": false,\n \"metadata\": {\n \"source\": \"app-server\"\n },\n \"status\": \"running\",\n \"created_at\": \"2026-08-26T09:58:00Z\",\n \"last_activity_at\": \"2026-08-26T10:15:00Z\",\n \"pending\": null\n}"
}
],
"structuredContent": {
"result": {
"id": "runner-01/codex/ses_01",
"machine_id": "runner-01",
"harness_type": "codex",
"cwd": "/workspace/app",
"title": "Repair deployment workflow",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
},
"agent": "default",
"read_only": false,
"metadata": {
"source": "app-server"
},
"status": "running",
"created_at": "2026-08-26T09:58:00Z",
"last_activity_at": "2026-08-26T10:15:00Z",
"pending": null
}
},
"isError": false
}
}send_message
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "send_message",
"arguments": {
"session_id": "runner-01/codex/ses_01",
"text": "Continue with the migration plan."
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"command_id\": \"9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb\",\n \"accepted\": true\n}"
}
],
"structuredContent": {
"result": {
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"accepted": true
}
},
"isError": false
}
}interrupt_session
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "interrupt_session",
"arguments": {
"session_id": "runner-01/codex/ses_01"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"command_id\": \"9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb\",\n \"accepted\": true\n}"
}
],
"structuredContent": {
"result": {
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"accepted": true
}
},
"isError": false
}
}create_session
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "create_session",
"arguments": {
"machine_id": "runner-01",
"harness_type": "codex",
"cwd": "/workspace/app",
"title": "Investigate CI",
"provider_id": "openai",
"model_id": "gpt-5.4"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"command_id\": \"9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb\",\n \"accepted\": true\n}"
}
],
"structuredContent": {
"result": {
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"accepted": true
}
},
"isError": false
}
}list_machines
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_machines",
"arguments": {}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "[\n {\n \"id\": \"runner-01\",\n \"name\": \"Studio Mac\",\n \"owner_account_id\": \"account-01\",\n \"last_seen_at\": \"2026-08-26T10:15:00Z\",\n \"status\": \"online\",\n \"harnesses\": [\n \"codex\",\n \"opencode\"\n ]\n }\n]"
}
],
"structuredContent": {
"result": [
{
"id": "runner-01",
"name": "Studio Mac",
"owner_account_id": "account-01",
"last_seen_at": "2026-08-26T10:15:00Z",
"status": "online",
"harnesses": [
"codex",
"opencode"
]
}
]
},
"isError": false
}
}list_harness_capabilities
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_harness_capabilities",
"arguments": {
"machine_id": "runner-01"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "[\n {\n \"machine_id\": \"runner-01\",\n \"harness_type\": \"codex\",\n \"can_create_session\": true,\n \"can_send_message\": true,\n \"can_respond_to_approval\": true,\n \"directories\": [\n \"/workspace/app\"\n ],\n \"models\": [\n {\n \"provider_id\": \"openai\",\n \"model_id\": \"gpt-5.4\",\n \"name\": \"GPT-5.4\"\n }\n ],\n \"reported_at\": \"2026-08-26T10:15:00Z\"\n }\n]"
}
],
"structuredContent": {
"result": [
{
"machine_id": "runner-01",
"harness_type": "codex",
"can_create_session": true,
"can_send_message": true,
"can_respond_to_approval": true,
"directories": [
"/workspace/app"
],
"models": [
{
"provider_id": "openai",
"model_id": "gpt-5.4",
"name": "GPT-5.4"
}
],
"reported_at": "2026-08-26T10:15:00Z"
}
]
},
"isError": false
}
}get_usage
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_usage",
"arguments": {
"from": "2026-08-26T00:00:00Z",
"to": "2026-08-26T23:59:59Z",
"harness": "codex"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"range\": {\n \"from\": \"2026-08-26T00:00:00Z\",\n \"to\": \"2026-08-26T23:59:59Z\"\n },\n \"totals\": {\n \"tokens\": {\n \"input\": 1200,\n \"output\": 300,\n \"reasoning\": null,\n \"cache_read\": 450,\n \"cache_write\": null,\n \"total\": 1500\n },\n \"cost\": {\n \"status\": \"estimated\",\n \"coverage\": \"partial\",\n \"priced_samples\": 2,\n \"unavailable_samples\": 1,\n \"amount\": 0.08,\n \"currency\": \"USD\"\n }\n },\n \"breakdowns\": {\n \"by_machine\": [],\n \"by_harness\": [\n {\n \"key\": \"codex\",\n \"tokens\": {\n \"input\": 1200,\n \"output\": 300,\n \"reasoning\": null,\n \"cache_read\": 450,\n \"cache_write\": null,\n \"total\": 1500\n },\n \"cost\": {\n \"status\": \"estimated\",\n \"coverage\": \"partial\",\n \"priced_samples\": 2,\n \"unavailable_samples\": 1,\n \"amount\": 0.08,\n \"currency\": \"USD\"\n }\n }\n ],\n \"by_session\": [],\n \"by_provider\": [],\n \"by_model\": []\n },\n \"context\": [\n {\n \"machine_id\": \"runner-01\",\n \"session_id\": \"runner-01/codex/ses_01\",\n \"harness\": \"codex\",\n \"provider\": \"openai\",\n \"model\": \"gpt-5\",\n \"timestamp\": \"2026-08-26T10:15:00Z\",\n \"window_size\": 200000,\n \"used_tokens\": 1500\n }\n ],\n \"quota\": [\n {\n \"machine_id\": \"runner-01\",\n \"harness\": \"codex\",\n \"provider\": \"openai\",\n \"timestamp\": \"2026-08-26T10:15:00Z\",\n \"windows\": [\n {\n \"name\": \"five_hour\",\n \"used_percent\": 35,\n \"resets_at\": \"2026-08-26T15:00:00Z\"\n }\n ]\n }\n ],\n \"samples_count\": 3\n}"
}
],
"structuredContent": {
"result": {
"range": {
"from": "2026-08-26T00:00:00Z",
"to": "2026-08-26T23:59:59Z"
},
"totals": {
"tokens": {
"input": 1200,
"output": 300,
"reasoning": null,
"cache_read": 450,
"cache_write": null,
"total": 1500
},
"cost": {
"status": "estimated",
"coverage": "partial",
"priced_samples": 2,
"unavailable_samples": 1,
"amount": 0.08,
"currency": "USD"
}
},
"breakdowns": {
"by_machine": [],
"by_harness": [
{
"key": "codex",
"tokens": {
"input": 1200,
"output": 300,
"reasoning": null,
"cache_read": 450,
"cache_write": null,
"total": 1500
},
"cost": {
"status": "estimated",
"coverage": "partial",
"priced_samples": 2,
"unavailable_samples": 1,
"amount": 0.08,
"currency": "USD"
}
}
],
"by_session": [],
"by_provider": [],
"by_model": []
},
"context": [
{
"machine_id": "runner-01",
"session_id": "runner-01/codex/ses_01",
"harness": "codex",
"provider": "openai",
"model": "gpt-5",
"timestamp": "2026-08-26T10:15:00Z",
"window_size": 200000,
"used_tokens": 1500
}
],
"quota": [
{
"machine_id": "runner-01",
"harness": "codex",
"provider": "openai",
"timestamp": "2026-08-26T10:15:00Z",
"windows": [
{
"name": "five_hour",
"used_percent": 35,
"resets_at": "2026-08-26T15:00:00Z"
}
]
}
],
"samples_count": 3
}
},
"isError": false
}
}get_pending
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_pending",
"arguments": {
"session_id": "runner-01/codex/ses_01"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"type\": \"approval\",\n \"id\": \"per_01\",\n \"session_id\": \"runner-01/codex/ses_01\",\n \"tool_name\": \"shell\",\n \"tool_input_summary\": \"bun run migrate\",\n \"requested_at\": \"2026-08-26T10:15:00Z\"\n}"
}
],
"structuredContent": {
"result": {
"type": "approval",
"id": "per_01",
"session_id": "runner-01/codex/ses_01",
"tool_name": "shell",
"tool_input_summary": "bun run migrate",
"requested_at": "2026-08-26T10:15:00Z"
}
},
"isError": false
}
}respond_to_pending
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "respond_to_pending",
"arguments": {
"session_id": "runner-01/codex/ses_01",
"pending_id": "per_01",
"response": "approve",
"scope": "once"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"command_id\": \"9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb\",\n \"accepted\": true\n}"
}
],
"structuredContent": {
"result": {
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"accepted": true
}
},
"isError": false
}
}list_pending
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_pending",
"arguments": {}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "[\n {\n \"pending\": {\n \"type\": \"approval\",\n \"id\": \"per_01\",\n \"session_id\": \"runner-01/codex/ses_01\",\n \"tool_name\": \"shell\",\n \"tool_input_summary\": \"bun run migrate\",\n \"requested_at\": \"2026-08-26T10:15:00Z\"\n },\n \"session_id\": \"runner-01/codex/ses_01\",\n \"machine_id\": \"runner-01\",\n \"harness_type\": \"codex\",\n \"cwd\": \"/workspace/app\",\n \"title\": \"Repair deployment workflow\",\n \"model\": {\n \"provider_id\": \"openai\",\n \"model_id\": \"gpt-5.4\"\n },\n \"agent\": \"default\",\n \"read_only\": false,\n \"actionable\": true\n }\n]"
}
],
"structuredContent": {
"result": [
{
"pending": {
"type": "approval",
"id": "per_01",
"session_id": "runner-01/codex/ses_01",
"tool_name": "shell",
"tool_input_summary": "bun run migrate",
"requested_at": "2026-08-26T10:15:00Z"
},
"session_id": "runner-01/codex/ses_01",
"machine_id": "runner-01",
"harness_type": "codex",
"cwd": "/workspace/app",
"title": "Repair deployment workflow",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
},
"agent": "default",
"read_only": false,
"actionable": true
}
]
},
"isError": false
}
}respond_to_pending_by_id
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "respond_to_pending_by_id",
"arguments": {
"pending_id": "per_01",
"response": "approve",
"scope": "once"
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"command_id\": \"9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb\",\n \"completed\": true\n}"
}
],
"structuredContent": {
"result": {
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"completed": true
}
},
"isError": false
}
}get_transcript
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_transcript",
"arguments": {
"session_id": "runner-01/codex/ses_01",
"limit": 100
}
}
}{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"events\": [\n {\n \"id\": \"evt_01\",\n \"session_id\": \"runner-01/codex/ses_01\",\n \"seq\": 42,\n \"ts\": \"2026-08-26T10:15:00Z\",\n \"type\": \"agent_message\",\n \"payload\": {\n \"text\": \"The tests pass.\"\n }\n }\n ],\n \"next_cursor\": null\n}"
}
],
"structuredContent": {
"result": {
"events": [
{
"id": "evt_01",
"session_id": "runner-01/codex/ses_01",
"seq": 42,
"ts": "2026-08-26T10:15:00Z",
"type": "agent_message",
"payload": {
"text": "The tests pass."
}
}
],
"next_cursor": null
}
},
"isError": false
}
}
Transcript resource
Load a full transcript as context instead of paging through tool calls.
session://runner-01%2Fcodex%2Fses_01/transcript
Schemas
These are the component schemas from /openapi.json.
Error
Every failed request returns this shape.
| Field | Type | Requirement | Description |
|---|---|---|---|
error | string | required | A human-readable failure reason. |
{
"error": "Session not found"
}Health
| Field | Type | Requirement | Description |
|---|---|---|---|
ok | boolean | required | True when the process serves traffic. |
{
"ok": true
}MachineStatus
Relay connection state of a paired runner.
Machine
One paired local runner.
| Field | Type | Requirement | Description |
|---|---|---|---|
id | string | required | Stable machine identifier. It is the first segment of every session ID. |
name | string | required | Display name that the runner reported during pairing. |
owner_account_id | string | required | Account that owns the runner. The server always overwrites a reported value with the token account. |
last_seen_at | string | required | Time of the last relay message or heartbeat. (format date-time) |
status | MachineStatus | required | Relay connection state of a paired runner. |
harnesses | string[] | optional | Harness types that the runner currently reports. |
{
"id": "runner-01",
"name": "Studio Mac",
"owner_account_id": "account-01",
"last_seen_at": "2026-08-26T10:15:00Z",
"status": "online",
"harnesses": [
"codex",
"opencode"
]
}SessionStatus
Reported by the runner. The server never infers a state.
SessionModel
Model identity reported by the harness.
| Field | Type | Requirement | Description |
|---|---|---|---|
provider_id | string | required | Harness model provider ID. |
model_id | string | required | Harness model ID. |
ApprovalScope
How long the harness keeps an approval decision.
ApprovalResolution
Approval
A tool call that waits for a human decision.
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "approval" | required | Discriminator. |
id | string | required | Exact approval ID. Send it back as pending_id. |
session_id | string | required | Namespaced session ID. |
tool_name | string | required | Tool that the harness wants to run. |
tool_input_summary | string | required | Short, elided description of the tool input. |
command | string | optional | Exact command reported separately by the local harness adapter. |
description | string | optional | Human-readable explanation reported by the local harness adapter. |
source | string | optional | Open-source CLI adapter or transport that observed the interaction. |
response_mode | string (remote · local) | optional | Whether this exact interaction has a live remote response handler. |
requested_at | string | required | Time the harness raised the approval. (format date-time) |
read_only | boolean | optional | True when the native response window has closed and the item can only be answered locally. |
expires_at | string | optional | Last instant at which the native harness accepts a remote response. (format date-time) |
resolved_at | string | optional | Time the approval was answered. (format date-time) |
resolution | ApprovalResolution | optional |
{
"type": "approval",
"id": "per_01",
"session_id": "runner-01/codex/ses_01",
"tool_name": "shell",
"tool_input_summary": "bun run migrate",
"requested_at": "2026-08-26T10:15:00Z"
}Question
A free-text or multiple-choice question from the harness.
| Field | Type | Requirement | Description |
|---|---|---|---|
type | "question" | required | Discriminator. |
id | string | required | Exact question ID. Send it back as pending_id. |
session_id | string | required | Namespaced session ID. |
prompt | string | required | Question text shown to the user. |
header | string | optional | Short harness-provided label. |
options | string[] | optional | Suggested answers, when the harness offers a closed set. |
option_details | object[] | optional | Suggested answers with optional descriptions. |
source | string | optional | Open-source CLI adapter or transport that observed the interaction. |
response_mode | string (remote · local) | optional | Whether this exact interaction has a live remote response handler. |
read_only | boolean | optional | True when the native response window has closed and the item can only be answered locally. |
expires_at | string | optional | Last instant at which the native harness accepts a remote response. (format date-time) |
requested_at | string | required | Time the harness raised the question. (format date-time) |
resolved_at | string | optional | Time the question was answered. (format date-time) |
response | string | optional | Answer that was relayed to the harness. |
{
"type": "question",
"id": "q_01",
"session_id": "runner-01/codex/ses_01",
"prompt": "Which region should I deploy first?",
"options": [
"eu-central",
"ap-southeast"
],
"requested_at": "2026-08-26T10:15:00Z"
}PendingInteraction
The one interaction a session currently waits on.
Session
One harness session on one machine.
| Field | Type | Requirement | Description |
|---|---|---|---|
id | string | required | Namespaced ID: {machine_id}/{harness_type}/{local_session_id}. |
machine_id | string | required | Machine that owns the session. |
harness_type | string | required | Harness that runs the session, for example opencode or claude-code. |
cwd | string | required | Working directory on the local machine. |
title | string | optional | Short human-readable title reported by the harness. It can be absent when the harness has no title. |
model | SessionModel | optional | Model identity reported by the harness. |
agent | string | optional | Harness-specific agent, mode, or persona name. |
read_only | boolean | optional | True when Rivetplane can observe this session but the adapter cannot control it. |
metadata | object | optional | Optional safe, harness-specific metadata. Consumers must ignore unknown keys. |
status | SessionStatus | required | Reported by the runner. The server never infers a state. |
created_at | string | required | Time the runner first reported the session. (format date-time) |
last_activity_at | string | required | Time of the most recent reported activity. (format date-time) |
pending | Approval | Question | null | required | The active approval or question, or null. See PendingInteraction. |
{
"id": "runner-01/codex/ses_01",
"machine_id": "runner-01",
"harness_type": "codex",
"cwd": "/workspace/app",
"title": "Repair deployment workflow",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
},
"agent": "default",
"read_only": false,
"metadata": {
"source": "app-server"
},
"status": "running",
"created_at": "2026-08-26T09:58:00Z",
"last_activity_at": "2026-08-26T10:15:00Z",
"pending": null
}PendingResponse
| Field | Type | Requirement | Description |
|---|---|---|---|
pending | Approval | Question | null | required | The active interaction, or null when the session waits on nothing. See PendingInteraction. |
{
"pending": {
"type": "approval",
"id": "per_01",
"session_id": "runner-01/codex/ses_01",
"tool_name": "shell",
"tool_input_summary": "bun run migrate",
"requested_at": "2026-08-26T10:15:00Z"
}
}PendingListItem
| Field | Type | Requirement | Description |
|---|---|---|---|
pending | PendingInteraction | required | The one interaction a session currently waits on. |
session_id | string | required | Session that owns the interaction. |
machine_id | string | required | Runner that owns the session. |
harness_type | string | required | Harness adapter type. |
cwd | string | required | Session working directory. |
title | string | optional | Harness-reported session title, when available. |
model | SessionModel | optional | Model identity reported by the harness. |
agent | string | optional | Harness-specific agent or mode, when available. |
read_only | boolean | optional | True when the session is observation-only. |
actionable | boolean | required | True when the online adapter reports response support. |
reason | string | optional | Why an included item is not actionable. |
reason_code | string | optional | Stable machine-readable reason, such as LOCAL_RESPONSE_REQUIRED, PENDING_EXPIRED, RUNNER_OFFLINE, or CAPABILITY_UNAVAILABLE. |
TranscriptEventType
Payload discriminator of a transcript event.
TranscriptEvent
One append-only transcript record. The payload shape follows the type.
| Field | Type | Requirement | Description |
|---|---|---|---|
id | string | required | Event ID that is unique inside the session. |
session_id | string | required | Namespaced session ID. |
seq | integer | required | Monotonic sequence number. Use it to order and to resume. (min 1) |
ts | string | required | Time the event happened on the machine. (format date-time) |
type | TranscriptEventType | required | Payload discriminator of a transcript event. |
payload | user_message | agent_message | tool_call | tool_result | permission_request | permission_response | status_change | required | Type-specific payload. |
{
"id": "evt_01",
"session_id": "runner-01/codex/ses_01",
"seq": 42,
"ts": "2026-08-26T10:15:00Z",
"type": "agent_message",
"payload": {
"text": "The tests pass."
}
}TranscriptPage
One page of transcript events in ascending sequence order.
| Field | Type | Requirement | Description |
|---|---|---|---|
events | TranscriptEvent[] | required | Events in ascending sequence order. |
next_cursor | string | null | required | Pass as cursor to read the next page. Null on the last page. |
{
"events": [
{
"id": "evt_01",
"session_id": "runner-01/codex/ses_01",
"seq": 42,
"ts": "2026-08-26T10:15:00Z",
"type": "agent_message",
"payload": {
"text": "The tests pass."
}
}
],
"next_cursor": null
}HarnessModel
One selectable model in a harness roster.
| Field | Type | Requirement | Description |
|---|---|---|---|
provider_id | string | required | Provider identifier reported by the harness. |
model_id | string | required | Model identifier reported by the harness. |
name | string | required | Display name. |
status | string (alpha · beta · deprecated · active) | optional | Lifecycle state reported by the harness. |
context_limit | integer | optional | Maximum input tokens. |
output_limit | integer | optional | Maximum output tokens. |
HarnessCapabilities
What one harness on one machine can do right now.
| Field | Type | Requirement | Description |
|---|---|---|---|
machine_id | string | required | Machine that reported the capabilities. |
harness_type | string | required | Harness type. |
can_create_session | boolean | required | True when the harness accepts remote session creation. |
can_send_message | boolean | optional | True when the adapter relays user messages. A discovery-only adapter omits it or reports false. |
can_interrupt | boolean | optional | True when the adapter can cancel the active turn. |
can_respond_to_approval | boolean | optional | True when the adapter can answer an approval. |
can_respond_to_question | boolean | optional | True when the adapter can answer a question. |
directories | string[] | required | Directories the harness allows. Session creation rejects any other value. |
models | HarnessModel[] | required | Models the caller may select for a new session. |
default_model | object | optional | Model the harness uses when the caller has no preference. |
reported_at | string | required | Time of the report. (format date-time) |
{
"machine_id": "runner-01",
"harness_type": "codex",
"can_create_session": true,
"can_send_message": true,
"can_respond_to_approval": true,
"directories": [
"/workspace/app"
],
"models": [
{
"provider_id": "openai",
"model_id": "gpt-5.4",
"name": "GPT-5.4"
}
],
"reported_at": "2026-08-26T10:15:00Z"
}UsageTokenCounts
Normalized incremental token contributions. Null means that no matching source reported the counter.
| Field | Type | Requirement | Description |
|---|---|---|---|
input | integer | null | required | |
output | integer | null | required | |
reasoning | integer | null | required | |
cache_read | integer | null | required | |
cache_write | integer | null | required | |
total | integer | null | required |
UsageCostSummary
Cost metadata, not an invoice. Any estimate makes status estimated. Partial coverage means some matching samples had no cost.
| Field | Type | Requirement | Description |
|---|---|---|---|
status | string (reported · estimated · unavailable) | required | reported, estimated, or unavailable. |
coverage | string (complete · partial · none) | required | Whether all, some, or no matching samples supplied cost. |
priced_samples | integer | required | Samples with a cost value. |
unavailable_samples | integer | required | Samples without a cost value. |
amount | number | optional | Sum when one currency is present. |
currency | string | optional | Currency of amount. |
by_currency | object[] | optional | Separate sums when multiple currencies are present. |
UsageAggregate
| Field | Type | Requirement | Description |
|---|---|---|---|
tokens | UsageTokenCounts | required | Normalized incremental token contributions. Null means that no matching source reported the counter. |
cost | UsageCostSummary | required | Cost metadata, not an invoice. Any estimate makes status estimated. Partial coverage means some matching samples had no cost. |
UsageBreakdown
| Field | Type | Requirement | Description |
|---|---|---|---|
key | string | required | Group key. |
tokens | UsageTokenCounts | required | Normalized incremental token contributions. Null means that no matching source reported the counter. |
cost | UsageCostSummary | required | Cost metadata, not an invoice. Any estimate makes status estimated. Partial coverage means some matching samples had no cost. |
UsageContext
| Field | Type | Requirement | Description |
|---|---|---|---|
machine_id | string | required | Machine that reported context. |
session_id | string | null | optional | |
harness | string | required | Harness that reported context. |
provider | string | optional | Model provider, when known. |
model | string | optional | Model identifier, when known. |
timestamp | string | required | Time of the latest context sample. (format date-time) |
window_size | integer | required | Maximum context tokens. (min 1) |
used_tokens | integer | null | optional |
UsageQuotaWindow
| Field | Type | Requirement | Description |
|---|---|---|---|
name | string | required | Source-defined quota window name. |
used_percent | number | null | optional | |
remaining | number | null | optional | |
limit | number | null | optional | |
resets_at | string | optional | Time when this quota window resets. (format date-time) |
UsageQuota
| Field | Type | Requirement | Description |
|---|---|---|---|
machine_id | string | required | Machine that reported quota. |
session_id | string | null | optional | |
harness | string | required | Harness that reported quota. |
provider | string | optional | Provider, when known. |
model | string | optional | Model, when known. |
timestamp | string | required | Time of the latest quota sample. (format date-time) |
windows | UsageQuotaWindow[] | required | Latest source-reported quota windows. |
UsageReport
Append-only usage aggregated for the requested range. Missing values stay null or unavailable.
| Field | Type | Requirement | Description |
|---|---|---|---|
range | object | required | Inclusive aggregation range. |
totals | UsageAggregate | required | |
breakdowns | object | required | Totals grouped by available identity fields. |
context | UsageContext[] | required | Latest context state for each matching identity. |
quota | UsageQuota[] | required | Latest quota state for each matching identity. |
samples_count | integer | required | Unique samples included after event ID deduplication. |
{
"range": {
"from": "2026-08-26T00:00:00Z",
"to": "2026-08-26T23:59:59Z"
},
"totals": {
"tokens": {
"input": 1200,
"output": 300,
"reasoning": null,
"cache_read": 450,
"cache_write": null,
"total": 1500
},
"cost": {
"status": "estimated",
"coverage": "partial",
"priced_samples": 2,
"unavailable_samples": 1,
"amount": 0.08,
"currency": "USD"
}
},
"breakdowns": {
"by_machine": [],
"by_harness": [
{
"key": "codex",
"tokens": {
"input": 1200,
"output": 300,
"reasoning": null,
"cache_read": 450,
"cache_write": null,
"total": 1500
},
"cost": {
"status": "estimated",
"coverage": "partial",
"priced_samples": 2,
"unavailable_samples": 1,
"amount": 0.08,
"currency": "USD"
}
}
],
"by_session": [],
"by_provider": [],
"by_model": []
},
"context": [
{
"machine_id": "runner-01",
"session_id": "runner-01/codex/ses_01",
"harness": "codex",
"provider": "openai",
"model": "gpt-5",
"timestamp": "2026-08-26T10:15:00Z",
"window_size": 200000,
"used_tokens": 1500
}
],
"quota": [
{
"machine_id": "runner-01",
"harness": "codex",
"provider": "openai",
"timestamp": "2026-08-26T10:15:00Z",
"windows": [
{
"name": "five_hour",
"used_percent": 35,
"resets_at": "2026-08-26T15:00:00Z"
}
]
}
],
"samples_count": 3
}CreateSessionRequest
| Field | Type | Requirement | Description |
|---|---|---|---|
cwd | string | required | Working directory. It must appear in the harness capability report. |
title | string | optional | Optional session title. |
model | object | required | Model to run. It must appear in the reported roster. |
{
"cwd": "/workspace/app",
"title": "Investigate CI",
"model": {
"provider_id": "openai",
"model_id": "gpt-5.4"
}
}SendMessageRequest
| Field | Type | Requirement | Description |
|---|---|---|---|
text | string | required | Message text. It must not be empty or only whitespace. (min length 1) |
{
"text": "Continue with the migration plan."
}RespondToPendingRequest
| Field | Type | Requirement | Description |
|---|---|---|---|
pending_id | string | required | Exact ID of the active interaction. A stale ID returns 409. |
response | string | required | approve, deny, or the free text answer to a question. |
scope | ApprovalScope | optional | How long the harness keeps an approval decision. |
{
"pending_id": "per_01",
"response": "approve",
"scope": "once"
}RespondByPendingIdRequest
| Field | Type | Requirement | Description |
|---|---|---|---|
response | string | required | approve, deny, or the free text answer to a question. |
scope | ApprovalScope | optional | How long the harness keeps an approval decision. |
CommandCompleted
| Field | Type | Requirement | Description |
|---|---|---|---|
command_id | string | required | Command ID reported by the runner. |
completed | true | required | The runner reported success. |
CommandAccepted
The control plane relayed a command to the runner. It does not mean the harness finished the work.
| Field | Type | Requirement | Description |
|---|---|---|---|
command_id | string | required | Correlate this with command_sent and command_result events. |
accepted | true | required | Always true. |
{
"command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
"accepted": true
}MachineRetired
| Field | Type | Requirement | Description |
|---|---|---|---|
retired | true | required | Always true. |
{
"retired": true
}MachineTokenRequest
| Field | Type | Requirement | Description |
|---|---|---|---|
code | string | required | Authorization code from the loopback callback. |
code_verifier | string | required | PKCE verifier for the S256 challenge. |
redirect_uri | string | required | The exact loopback callback used in the authorization request. |
{
"code": "1kQ7yq3fJ2Xo9m8vTn0pQ5rW6sD1aZ4c",
"code_verifier": "b3T3wM9dLp1sV2xY7hR4nJ6kF8gC0uQ5",
"redirect_uri": "http://127.0.0.1:45678/callback"
}MachineTokenResponse
| Field | Type | Requirement | Description |
|---|---|---|---|
access_token | string | required | Machine bearer token. The server stores only its hash. |
token_type | "Bearer" | required | |
machine_id | string | required | Stable machine ID for this device. |
owner_account_id | string | required | Account that owns the machine. |
{
"access_token": "shown-once",
"token_type": "Bearer",
"machine_id": "runner-01",
"owner_account_id": "account-01"
}DeviceCodeRequest
| Field | Type | Requirement | Description |
|---|---|---|---|
device_name | string | required | Controller name. (min length 1) |
device_id | string | required | Stable controller identity. (format uuid) |
DeviceCodeResponse
| Field | Type | Requirement | Description |
|---|---|---|---|
device_code | string | required | Secret code used only at the token endpoint. |
user_code | string | required | Short code shown to the user. |
verification_uri | string | required | Page where the user enters the code. The code is separate so it does not enter URL logs. (format uri) |
expires_in | 600 | required | Grant lifetime in seconds. |
interval | integer | required | Minimum polling interval in seconds. (min 5) |
DeviceTokenRequest
| Field | Type | Requirement | Description |
|---|---|---|---|
device_code | string | required | Secret device code from the issue response. |
ConsumerDevice
| Field | Type | Requirement | Description |
|---|---|---|---|
id | string | required | Server device record. (format uuid) |
device_id | string | required | Stable controller identity. (format uuid) |
name | string | required | Controller name. |
scopes | string (sessions:list · sessions:read · transcripts:read · usage:read · messages:send · pending:respond)[] | required | Fixed minimum consumer permissions. |
created_at | string | required | Pairing time. (format date-time) |
last_used_at | string | null | required | Most recent token use, or null. |
revoked_at | string | null | required | Revocation time, or null. |
DeviceTokenResponse
| Field | Type | Requirement | Description |
|---|---|---|---|
access_token | string | required | Consumer bearer token. The server stores only its SHA-256 hash. |
token_type | "Bearer" | required | Bearer token type. |
expires_in | null | required | Null because revocation controls token lifetime. |
scope | "sessions:list sessions:read transcripts:read usage:read messages:send pending:respond" | required | Space-separated permissions. |
device | ConsumerDevice | required | Paired controller record. |
DeviceTokenError
| Field | Type | Requirement | Description |
|---|---|---|---|
error | string (authorization_pending · slow_down · expired_token · access_denied · invalid_grant) | required | Device authorization state. |
interval | integer | optional | New minimum polling interval after slow_down. |
Revoked
| Field | Type | Requirement | Description |
|---|---|---|---|
revoked | true | required | Always true. |
StripeWebhookAck
| Field | Type | Requirement | Description |
|---|---|---|---|
received | true | required |
{
"received": true
}JsonRpcRequest
MCP Streamable HTTP request.
| Field | Type | Requirement | Description |
|---|---|---|---|
jsonrpc | "2.0" | required | |
id | string | integer | null | optional | Omit it only for notifications. |
method | string | required | initialize, ping, tools/list, tools/call, resources/list, resources/templates/list, resources/read, or a notifications/* method. |
params | object | optional | Method parameters. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}JsonRpcResponse
| Field | Type | Requirement | Description |
|---|---|---|---|
jsonrpc | "2.0" | required | |
id | string | integer | null | required | Echoes the request ID. |
result | object | required | Method result. Transport and authorization failures use HTTP status codes with the Error body. |
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "list_sessions"
}
]
}
}Html
A server-rendered HTML page.
Errors and safe control
Every failed request returns application/json with one field.
{
"error": "pending_id does not match the current interaction"
}
| Status | Meaning | What to do |
|---|---|---|
| 400 | Invalid body, cursor, model, or working directory | Read the capability report and correct the request. |
| 401 | Missing or invalid bearer token | Send a current token in the Authorization header. |
| 403 | The token cannot access this account, machine, or session, or it lacks the scope | Use a token of the owning account with the required scope. |
| 404 | Unknown machine, session, MCP method, tool, or resource | List the parent collection again. The runner may have been retired. |
| 409 | Runner offline, no active interaction, stale pending_id, or the harness cannot create sessions | Re-read state and retry with current values. |
| 503 | Stripe Billing is not configured | Expected on self-hosted deployments without billing. |
Always read the current pending interaction and answer with its exact pending_id. Never approve "whatever is pending." The local harness stays the permission authority; Rivetplane relays a decision and never bypasses a permission.
Every relayed message, approval response, session creation, and runner retirement is written to the audit log with the acting principal.