Developer documentation

Rivetplane API

One authenticated surface for machines, harness capabilities, sessions, transcripts, approvals, questions, streaming events, and MCP. Every operation below states its authentication, parameters, request schema, response schema, status codes, and a copyable example.

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.

CredentialHeader or cookieIssued byCan do
API tokenAuthorization: Bearer …Dashboard, POST /api-tokensEvery REST, SSE, WebSocket, and MCP operation for its account, with read and write scope
Machine tokenAuthorization: Bearer …POST /v1/auth/tokenOnly the relay channel and its own machine's sessions. It cannot retire a runner.
Browser sessionhcp_access, hcp_refresh, hcp_csrf cookiesSupabase sign-inDashboard 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

GET/health

Returns 200 as soon as the HTTP listener serves traffic. Deployment health checks use it.

Authentication: None. The endpoint is public.

Responses

StatusContent typeBodyMeaning
200application/jsonHealthThe server is live

Example request

curl "https://rivetplane.com/health"

Example response

{
  "ok": true
}
GET/openapi.json

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

StatusContent typeBodyMeaning
200application/jsonobjectAn 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."
      }
    }
  }
}
GET/llms.txt

A plain-text summary of authentication, endpoints, request bodies, responses, streams, and MCP tools for agent consumers.

Authentication: None. The endpoint is public.

Responses

StatusContent typeBodyMeaning
200text/plainstringPlain-text API summary

Example request

curl "https://rivetplane.com/llms.txt"

Example response

# Rivetplane

## Authentication
Authorization: Bearer <API_TOKEN>
...
GET/docs

The rendered reference. Every operation on it comes from the same definitions as /openapi.json.

Authentication: None. The endpoint is public.

Responses

StatusContent typeBodyMeaning
200text/htmlHtmlThe API reference page

Example request

curl "https://rivetplane.com/docs"

Example response

<!doctype html> ... Rivetplane API ...
GET/capabilities

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

StatusContent typeBodyMeaning
200text/htmlHtmlThe capability overview page

Example request

curl "https://rivetplane.com/capabilities"

Example response

<!doctype html> ... Everything on the plane. ...
GET/ui.js

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

StatusContent typeBodyMeaning
200text/javascriptstringJavaScript for the documentation, capability, and dashboard pages

Example request

curl "https://rivetplane.com/ui.js"

Example response

(() => { /* copy buttons */ })();

Machines

GET/v1/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

StatusContent typeBodyMeaning
200application/jsonMachine[]Machines visible to the token
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe 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"
    ]
  }
]
POST/v1/machines/{machine_id}/retire

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

NameTypeRequirementDescription
machine_idstringrequiredThe runner to retire. Example: runner-01

Responses

StatusContent typeBodyMeaning
200application/jsonMachineRetiredThe runner is retired
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
404application/jsonErrorThe target does not exist.
409application/jsonErrorThe 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

POST/v1/auth/device/code

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.

FieldTypeRequirementDescription
device_namestringrequiredController name. (min length 1)
device_idstringrequiredStable controller identity. (format uuid)
{
  "device_name": "Desk controller",
  "device_id": "4f61c8c8-55c3-4a36-b8eb-11855267982e"
}

Responses

StatusContent typeBodyMeaning
200application/jsonDeviceCodeResponseA device authorization request
400application/jsonErrorInvalid device name or identity
429application/jsonErrorToo 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
}
POST/v1/auth/device/token

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.

FieldTypeRequirementDescription
device_codestringrequiredSecret device code from the issue response.
{
  "device_code": "base64url-secret"
}

Responses

StatusContent typeBodyMeaning
200application/jsonDeviceTokenResponseA minimum-scope consumer token
400application/jsonDeviceTokenErrorPending, slow down, expired, denied, or invalid/replayed grant
429application/jsonErrorToo 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
}
GET/v1/consumer-devices

Lists controller identities and status. Consumer tokens cannot use this token-management endpoint.

Authentication: Bearer token in the Authorization header. Scope: read.

Responses

StatusContent typeBodyMeaning
200application/jsonConsumerDevice[]Paired controllers
401application/jsonErrorMissing or invalid bearer token
403application/jsonErrorAn account API token is required

Example request

curl "https://rivetplane.com/v1/consumer-devices" \
  -H "Authorization: Bearer $RIVETPLANE_TOKEN"

Example response

[]
POST/v1/consumer-devices/{device_id}/revoke

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

NameTypeRequirementDescription
device_idstringrequiredConsumer device record ID.

Responses

StatusContent typeBodyMeaning
200application/jsonRevokedThe controller was revoked
401application/jsonErrorMissing or invalid bearer token
403application/jsonErrorAn account API token is required
404application/jsonErrorConsumer 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
}
POST/v1/auth/token

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.

FieldTypeRequirementDescription
codestringrequiredAuthorization code from the loopback callback.
code_verifierstringrequiredPKCE verifier for the S256 challenge.
redirect_uristringrequiredThe 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

StatusContent typeBodyMeaning
200application/jsonMachineTokenResponseA machine bearer token
400application/jsonobjectinvalid_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"
}
GET/authorize

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

NameTypeRequirementDescription
redirect_uristringrequiredLoopback callback. Only http://127.0.0.1:PORT/callback or the IPv6 loopback is allowed. Example: http://127.0.0.1:45678/callback
statestringrequiredOpaque client state that is returned on the callback. Example: b7f1c2
code_challengestringrequiredPKCE S256 challenge, 43 base64url characters. Example: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
code_challenge_methodstring (S256)optionalOnly S256 is used. Example: S256
machine_namestringoptionalRunner name shown on the prompt. Example: Studio Mac
device_idstringoptionalStable device UUID. It keeps one machine identity across re-pairing.
previous_machine_idstringoptionalMachine ID from an earlier pairing of the same device.

Responses

StatusContent typeBodyMeaning
200text/htmlHtmlThe authorization prompt
303——Not signed in. Redirected to /login with return_to.
400text/htmlHtmlInvalid 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 ...
POST/authorize

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.

FieldTypeRequirementDescription
csrfstringrequiredCSRF token from the prompt.
redirect_uristringrequiredLoopback callback.
statestringrequiredClient state.
code_challengestringrequiredPKCE S256 challenge.
machine_namestringoptionalRunner name.
device_idstringoptionalStable device UUID.
previous_machine_idstringoptionalEarlier 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

StatusContent typeBodyMeaning
303——Redirected to the loopback callback with code and state
400text/htmlHtmlInvalid 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
GET/device

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

StatusContent typeBodyMeaning
200text/htmlHtmlCode entry page
303——Sign in before review

Example request

curl "https://rivetplane.com/device"

Example response

<!doctype html> ... Pair a controller ...
POST/device/review

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.

FieldTypeRequirementDescription
csrfstringrequiredCSRF token.
user_codestringrequiredShort pairing code.
{
  "csrf": "cs_5f21c8",
  "user_code": "ABCD-EFGH"
}

Responses

StatusContent typeBodyMeaning
200text/htmlHtmlPairing review page
403text/htmlHtmlSession or CSRF failure
404text/htmlHtmlUnknown code
409text/htmlHtmlExpired 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 ...
POST/device

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.

FieldTypeRequirementDescription
csrfstringrequiredCSRF token.
user_codestringrequiredShort pairing code.
decisionstring (approved · denied)required
{
  "csrf": "cs_5f21c8",
  "user_code": "ABCD-EFGH",
  "decision": "approved"
}

Responses

StatusContent typeBodyMeaning
200text/htmlHtmlDecision confirmation
400text/htmlHtmlInvalid decision or code
403text/htmlHtmlSession or CSRF failure
409text/htmlHtmlGrant 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

GET/v1/harness-capabilities

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

NameTypeRequirementDescription
machinestringoptionalOptional machine ID filter. Example: runner-01

Responses

StatusContent typeBodyMeaning
200application/jsonHarnessCapabilities[]Capability reports visible to the token
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe 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"
  }
]
GET/v1/machines/{machine_id}/harnesses/{harness}/capabilities

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

NameTypeRequirementDescription
machine_idstringrequiredThe paired runner ID. Example: runner-01
harnessstringrequiredThe harness type. Example: codex

Responses

StatusContent typeBodyMeaning
200application/jsonHarnessCapabilitiesThe capability report
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
404application/jsonErrorNo 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"
}
POST/v1/machines/{machine_id}/harnesses/{harness}/sessions

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

NameTypeRequirementDescription
machine_idstringrequiredThe paired runner ID. Example: runner-01
harnessstringrequiredThe reported harness type. Example: codex

Request body (application/json)

The directory and model to use.

FieldTypeRequirementDescription
cwdstringrequiredWorking directory. It must appear in the harness capability report.
titlestringoptionalOptional session title.
modelobjectrequiredModel 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

StatusContent typeBodyMeaning
202application/jsonCommandAcceptedThe creation command was relayed to the runner
400application/jsonErrorThe directory or model was not reported.
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
404application/jsonErrorThe target does not exist.
409application/jsonErrorThe 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

GET/v1/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

NameTypeRequirementDescription
fromstringoptionalInclusive RFC 3339 start. Defaults to 00:00 UTC today. Example: 2026-08-26T00:00:00Z
tostringoptionalInclusive RFC 3339 end. Defaults to now. Example: 2026-08-26T23:59:59Z
machinestringoptionalFilter by machine ID.
harnessstringoptionalFilter by harness type. Example: codex
sessionstringoptionalFilter by session ID.
providerstringoptionalFilter by provider.
modelstringoptionalFilter by model.

Responses

StatusContent typeBodyMeaning
200application/jsonUsageReportThe aggregated usage report
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
400application/jsonErrorfrom 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

GET/v1/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

NameTypeRequirementDescription
machinestringoptionalFilter by machine ID. Example: runner-01
harnessstringoptionalFilter by harness type. Example: codex
statusstring (running · waiting_input · waiting_approval · completed · error)optionalFilter by session status. Example: waiting_approval
cwdstringoptionalFilter by working directory. Example: /workspace/app
beforestringoptionalReturn sessions older than this ISO timestamp. Example: 2026-08-26T10:15:00Z
limitintegeroptionalReturn 1–1,000 sessions. Example: 20

Responses

StatusContent typeBodyMeaning
200application/jsonSession[]Sessions visible to the token
400application/jsonErrorbefore must be a valid timestamp, or limit must be an integer from 1 to 1000
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe 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"
    }
  }
]
GET/v1/sessions/{session_id}

Returns the harness type, directory, status, timestamps, and the active pending interaction.

Authentication: Bearer token in the Authorization header. Scope: read.

Path parameters

NameTypeRequirementDescription
session_idstringrequiredA percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01

Responses

StatusContent typeBodyMeaning
200application/jsonSessionThe session
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
404application/jsonErrorThe 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
}
GET/v1/sessions/{session_id}/transcript

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

NameTypeRequirementDescription
session_idstringrequiredA percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01

Query parameters

NameTypeRequirementDescription
cursorstringoptionalContinue from a prior page. Example: NDI
sincestringoptionalReturn events at or after an ISO timestamp. Example: 2026-08-26T10:15:00Z
limitintegeroptionalReturn 1–1,000 events. Example: 100

Responses

StatusContent typeBodyMeaning
200application/jsonTranscriptPageA transcript page
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
400application/jsonErrorThe cursor or limit is invalid.
404application/jsonErrorThe 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
}
GET/v1/sessions/{session_id}/transcript/stream

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

NameTypeRequirementDescription
session_idstringrequiredA percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01

Headers

NameTypeRequirementDescription
AuthorizationstringrequiredBearer API token. Example: Bearer $RIVETPLANE_TOKEN

Responses

StatusContent typeBodyMeaning
200text/event-streamTranscriptEventAn open Server-Sent Events stream of TranscriptEvent values
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
404application/jsonErrorThe 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."}}
GET/v1/sessions/{session_id}/pending

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

NameTypeRequirementDescription
session_idstringrequiredA percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01

Responses

StatusContent typeBodyMeaning
200application/jsonPendingResponseThe active interaction, or null
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
404application/jsonErrorThe 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"
  }
}
POST/v1/sessions/{session_id}/messages

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

NameTypeRequirementDescription
session_idstringrequiredA percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01

Request body (application/json)

The message to relay.

FieldTypeRequirementDescription
textstringrequiredMessage text. It must not be empty or only whitespace. (min length 1)
{
  "text": "Continue with the migration plan."
}

Responses

StatusContent typeBodyMeaning
202application/jsonCommandAcceptedThe message command was relayed to the runner
400application/jsonErrortext must not be empty
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
404application/jsonErrorThe target does not exist.
409application/jsonErrorThe 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
}
POST/v1/sessions/{session_id}/pending/respond

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

NameTypeRequirementDescription
session_idstringrequiredA percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01

Request body (application/json)

The exact interaction ID and the decision.

FieldTypeRequirementDescription
pending_idstringrequiredExact ID of the active interaction. A stale ID returns 409.
responsestringrequiredapprove, deny, or the free text answer to a question.
scopeApprovalScopeoptionalHow long the harness keeps an approval decision.
{
  "pending_id": "per_01",
  "response": "approve",
  "scope": "once"
}

Responses

StatusContent typeBodyMeaning
202application/jsonCommandAcceptedThe response command was relayed to the runner
400application/jsonErrorresponse is required
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
404application/jsonErrorThe target does not exist.
409application/jsonErrorThe 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
}
POST/v1/sessions/{session_id}/interrupt

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

NameTypeRequirementDescription
session_idstringrequiredA percent-encoded namespaced session ID. Example: runner-01%2Fcodex%2Fses_01

Responses

StatusContent typeBodyMeaning
202application/jsonCommandAcceptedThe interrupt command was relayed to the runner
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
404application/jsonErrorThe target does not exist.
409application/jsonErrorThe 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

GET/v1/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

NameTypeRequirementDescription
include_non_actionablebooleanoptionalSet true to include read-only, expired, and offline items with a reason. Example: false

Responses

StatusContent typeBodyMeaning
200application/jsonPendingListItem[]Pending items visible to the token
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe 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
  }
]
POST/v1/pending/{pending_id}/respond

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

NameTypeRequirementDescription
pending_idstringrequiredExact ID from GET /v1/pending.

Request body (application/json)

The decision or answer.

FieldTypeRequirementDescription
responsestringrequiredapprove, deny, or the free text answer to a question.
scopeApprovalScopeoptionalHow long the harness keeps an approval decision.
{
  "response": "approve",
  "scope": "once"
}

Responses

StatusContent typeBodyMeaning
200application/jsonCommandCompletedThe runner applied the response
400application/jsonErrorresponse is required
401application/jsonErrorThe bearer token is missing, expired, or invalid.
403application/jsonErrorThe token cannot access this account, machine, or session.
404application/jsonErrorThe target does not exist.
409application/jsonErrorThe runner is offline or the target state changed.
504application/jsonErrorThe 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

POST/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.

FieldTypeRequirementDescription
jsonrpc"2.0"required
idstring | integer | nulloptionalOmit it only for notifications.
methodstringrequiredinitialize, ping, tools/list, tools/call, resources/list, resources/templates/list, resources/read, or a notifications/* method.
paramsobjectoptionalMethod parameters.
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

Responses

StatusContent typeBodyMeaning
200application/jsonJsonRpcResponseA JSON-RPC 2.0 result
202——A notification was accepted. The body is empty.
400application/jsonErrorTool name is required, or a required argument is missing
401application/jsonErrorMissing or invalid bearer token
403application/jsonErrorToken cannot access this account, machine, or session
404application/jsonErrorUnknown MCP method, tool, or resource
409application/jsonErrorMachine 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

POST/v1/billing/webhook

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

NameTypeRequirementDescription
Stripe-SignaturestringrequiredStripe 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

StatusContent typeBodyMeaning
200application/jsonStripeWebhookAckThe event was accepted
400application/jsonErrorStripe webhook signature or payload is not valid
503application/jsonErrorStripe 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
}
POST/billing/checkout

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.

FieldTypeRequirementDescription
csrfstringrequiredCSRF token from the dashboard.
planstring (pro · team)optionalPlan to purchase. The default is pro.
{
  "csrf": "cs_5f21c8",
  "plan": "pro"
}

Responses

StatusContent typeBodyMeaning
303——Redirected to Stripe Checkout, or to /login when the visitor is anonymous
403text/htmlHtmlCSRF mismatch
502text/htmlHtmlStripe rejected the request
503text/htmlHtmlBilling 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_...
POST/billing/portal

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.

FieldTypeRequirementDescription
csrfstringrequiredCSRF token from the dashboard.
{
  "csrf": "cs_5f21c8"
}

Responses

StatusContent typeBodyMeaning
303——Redirected to the Stripe customer portal, or to /login when the visitor is anonymous
403text/htmlHtmlCSRF mismatch
502text/htmlHtmlStripe rejected the request
503text/htmlHtmlBilling 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

GET/

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

StatusContent typeBodyMeaning
200text/htmlHtmlThe landing page, or the dashboard for a signed-in session

Example request

curl "https://rivetplane.com/"

Example response

<!doctype html> ... Any harness. One plane. ...
GET/login

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

NameTypeRequirementDescription
return_tostringoptionalSame-origin path to open after sign-in. Example: /docs

Responses

StatusContent typeBodyMeaning
200text/htmlHtmlThe sign-in form

Example request

curl "https://rivetplane.com/login"

Example response

<!doctype html> ... auth-page ...
GET/signup

Renders the account creation form. Supabase Auth owns the account record and the confirmation email.

Authentication: None. The endpoint is public.

Query parameters

NameTypeRequirementDescription
return_tostringoptionalSame-origin path to open after sign-up. Example: /

Responses

StatusContent typeBodyMeaning
200text/htmlHtmlThe sign-up form

Example request

curl "https://rivetplane.com/signup"

Example response

<!doctype html> ... auth-page ...
POST/auth/login

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.

FieldTypeRequirementDescription
emailstringrequiredAccount email.
passwordstringrequiredPassword with at least 12 characters.
return_tostringoptionalSame-origin path to open after sign-in.
{
  "email": "dev@example.com",
  "password": "correct horse battery staple",
  "return_to": "/"
}

Responses

StatusContent typeBodyMeaning
303——Signed in. The response sets session cookies and redirects.
400text/htmlHtmlInvalid email or short password
401text/htmlHtmlAuthentication 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
POST/auth/signup

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.

FieldTypeRequirementDescription
emailstringrequiredAccount email.
passwordstringrequiredPassword with at least 12 characters.
return_tostringoptionalSame-origin path to open after sign-up.
{
  "email": "dev@example.com",
  "password": "correct horse battery staple",
  "return_to": "/"
}

Responses

StatusContent typeBodyMeaning
303——Account created with an active session
202text/htmlHtmlAccount created. A confirmation email was sent.
400text/htmlHtmlInvalid email or short password
409text/htmlHtmlThe 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
POST/logout

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.

FieldTypeRequirementDescription
csrfstringrequiredCSRF token from the page form.
{
  "csrf": "cs_5f21c8"
}

Responses

StatusContent typeBodyMeaning
303——Signed out and redirected to /login
403text/htmlHtmlMissing 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
POST/api-tokens

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.

FieldTypeRequirementDescription
csrfstringrequiredCSRF token from the dashboard form.
{
  "csrf": "cs_5f21c8"
}

Responses

StatusContent typeBodyMeaning
201text/htmlHtmlThe token page. The secret is shown once.
303——Not signed in. Redirected to /login.
403text/htmlHtmlCSRF 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> ...
POST/machines/{machine_id}/retire

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

NameTypeRequirementDescription
machine_idstringrequiredMachine ID from GET /v1/machines. Example: runner-01

Request body (application/x-www-form-urlencoded)

Form field: csrf.

FieldTypeRequirementDescription
csrfstringrequiredCSRF token from the runner card.
{
  "csrf": "cs_5f21c8"
}

Responses

StatusContent typeBodyMeaning
303——The runner is retired. Redirected to the dashboard.
403text/htmlHtmlMissing session or CSRF mismatch
404text/htmlHtmlRunner 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: /
POST/consumer-devices/{device_id}/revoke

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

NameTypeRequirementDescription
device_idstringrequiredConsumer device record ID.

Request body (application/x-www-form-urlencoded)

Form field: csrf.

FieldTypeRequirementDescription
csrfstringrequiredCSRF token from the dashboard.
{
  "csrf": "cs_5f21c8"
}

Responses

StatusContent typeBodyMeaning
303——Controller revoked and dashboard opened
403text/htmlHtmlSession or CSRF failure
404text/htmlHtmlController 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."}}
WS/v1/events/stream

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

FrameDirectionMeaning
session_upsertserver → clientA session was created or changed. data is the complete Session.
transcript_eventserver → clientA new transcript event. data is the TranscriptEvent.
usage_sampleserver → clientPrivacy-safe usage metadata was stored. data never contains prompt or file content.
machine_onlineserver → clientA runner connected. data is the Machine.
machine_offlineserver → clientA runner disconnected. data is empty.
session_removedserver → clientThe runner no longer reports the session.
harness_capabilitiesserver → clientA harness reported new directories or models.
command_sentserver → clientThe control plane relayed a command. Correlate with the 202 command_id.
command_resultserver → clientThe runner reported the outcome of a relayed command.

session_upsert

FieldTypeRequirementDescription
typestring (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result)requiredEvent kind.
tsstringrequiredTime the server published the event. (format date-time)
machine_idstringoptionalMachine the event belongs to.
session_idstringoptionalSession the event belongs to, when the event is session-scoped.
dataobjectoptionalMachine, 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

FieldTypeRequirementDescription
typestring (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result)requiredEvent kind.
tsstringrequiredTime the server published the event. (format date-time)
machine_idstringoptionalMachine the event belongs to.
session_idstringoptionalSession the event belongs to, when the event is session-scoped.
dataobjectoptionalMachine, 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

FieldTypeRequirementDescription
typestring (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result)requiredEvent kind.
tsstringrequiredTime the server published the event. (format date-time)
machine_idstringoptionalMachine the event belongs to.
session_idstringoptionalSession the event belongs to, when the event is session-scoped.
dataobjectoptionalMachine, 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

FieldTypeRequirementDescription
typestring (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result)requiredEvent kind.
tsstringrequiredTime the server published the event. (format date-time)
machine_idstringoptionalMachine the event belongs to.
session_idstringoptionalSession the event belongs to, when the event is session-scoped.
dataobjectoptionalMachine, 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

FieldTypeRequirementDescription
typestring (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result)requiredEvent kind.
tsstringrequiredTime the server published the event. (format date-time)
machine_idstringoptionalMachine the event belongs to.
session_idstringoptionalSession the event belongs to, when the event is session-scoped.
dataobjectoptionalMachine, Session, TranscriptEvent, HarnessCapabilities, or command payload, per type.
{
  "type": "machine_offline",
  "ts": "2026-08-26T10:15:00Z",
  "machine_id": "runner-01",
  "data": {}
}

session_removed

FieldTypeRequirementDescription
typestring (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result)requiredEvent kind.
tsstringrequiredTime the server published the event. (format date-time)
machine_idstringoptionalMachine the event belongs to.
session_idstringoptionalSession the event belongs to, when the event is session-scoped.
dataobjectoptionalMachine, 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

FieldTypeRequirementDescription
typestring (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result)requiredEvent kind.
tsstringrequiredTime the server published the event. (format date-time)
machine_idstringoptionalMachine the event belongs to.
session_idstringoptionalSession the event belongs to, when the event is session-scoped.
dataobjectoptionalMachine, 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

FieldTypeRequirementDescription
typestring (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result)requiredEvent kind.
tsstringrequiredTime the server published the event. (format date-time)
machine_idstringoptionalMachine the event belongs to.
session_idstringoptionalSession the event belongs to, when the event is session-scoped.
dataobjectoptionalMachine, 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

FieldTypeRequirementDescription
typestring (machine_online · machine_offline · session_upsert · session_removed · transcript_event · usage_sample · harness_capabilities · command_sent · command_result)requiredEvent kind.
tsstringrequiredTime the server published the event. (format date-time)
machine_idstringoptionalMachine the event belongs to.
session_idstringoptionalSession the event belongs to, when the event is session-scoped.
dataobjectoptionalMachine, 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

CodeReason
1008The token is not valid for this channel.
1001The server is stopping.
WS/v1/relay

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

FrameDirectionMeaning
machine.helloclient → serverFirst 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.heartbeatclient → serverKeeps the runner marked online.
harness.capabilitiesclient → serverReports directories, models, and whether remote session creation is allowed.
session.upsertclient → serverCreates or updates a session, including the pending interaction.
transcript.appendclient → serverAppends one transcript event. Repeating a sequence number is ignored, so reconnects are safe.
usage.sampleclient → serverAppends 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.removedclient → serverThe harness no longer has the session.
command.resultclient → serverReports the outcome of a server command. It is republished on the event stream.
relay.errorserver → clientA frame was rejected. The connection stays open.
command.send_messageserver → clientDeliver a user message to the harness session.
command.respond_to_pendingserver → clientAnswer the exact pending interaction.
command.interrupt_sessionserver → clientCancel the active turn.
command.create_sessionserver → clientStart a new harness session in a reported directory with a reported model.

machine.hello

FieldTypeRequirementDescription
type"machine.hello"required
protocol_version1required
machineMachinerequiredOne 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

FieldTypeRequirementDescription
type"machine.heartbeat"required
machine_idstringrequired
sent_atstringrequiredSend time. (format date-time)
{
  "type": "machine.heartbeat",
  "machine_id": "runner-01",
  "sent_at": "2026-08-26T10:15:00Z"
}

harness.capabilities

FieldTypeRequirementDescription
type"harness.capabilities"required
capabilitiesHarnessCapabilitiesrequiredWhat 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

FieldTypeRequirementDescription
type"session.upsert"required
sessionSessionrequiredOne 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

FieldTypeRequirementDescription
type"transcript.append"required
eventTranscriptEventrequiredOne 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

FieldTypeRequirementDescription
type"usage.sample"required
sampleobjectrequiredUsageSample 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

FieldTypeRequirementDescription
type"session.removed"required
session_idstringrequired
removed_atstringrequiredRemoval time. (format date-time)
{
  "type": "session.removed",
  "session_id": "runner-01/codex/ses_01",
  "removed_at": "2026-08-26T10:15:00Z"
}

command.result

FieldTypeRequirementDescription
type"command.result"required
command_idstringrequired
okbooleanrequired
errorstringoptional
resultobjectoptional
{
  "type": "command.result",
  "command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
  "ok": true,
  "result": {
    "session_id": "runner-01/codex/ses_01"
  }
}

relay.error

FieldTypeRequirementDescription
type"relay.error"required
errorstringrequired
{
  "type": "relay.error",
  "error": "Session does not belong to this machine"
}

command.send_message

FieldTypeRequirementDescription
type"command.send_message"required
command_idstringrequired
session_idstringrequired
textstringrequired
{
  "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

FieldTypeRequirementDescription
type"command.respond_to_pending"required
command_idstringrequired
session_idstringrequired
pending_idstringrequired
responsestringrequired
scopeApprovalScopeoptionalHow 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

FieldTypeRequirementDescription
type"command.interrupt_session"required
command_idstringrequired
session_idstringrequired
{
  "type": "command.interrupt_session",
  "command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
  "session_id": "runner-01/codex/ses_01"
}

command.create_session

FieldTypeRequirementDescription
type"command.create_session"required
command_idstringrequired
machine_idstringrequired
harness_typestringrequired
cwdstringrequired
titlestringoptional
modelobjectrequired
{
  "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

CodeReason
1003A frame was not valid JSON.
1008Machine token required, token mismatch, replaced by a newer connection, or the runner was retired.
1001The 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

RPCinitialize

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"
    }
  }
}
RPCnotifications/initialized

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)
RPCping

Returns an empty result.

Request

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "ping"
}

Response

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {}
}
RPCtools/list

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."
      }
    ]
  }
}
RPCtools/call

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
  }
}
RPCresources/list

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"
      }
    ]
  }
}
RPCresources/templates/list

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"
      }
    ]
  }
}
RPCresources/read

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

ToolScopeResultSame as REST
list_sessionsreadSession[]list_sessions
get_sessionreadSessionget_session
send_messageread + writeCommandAcceptedsend_message
interrupt_sessionread + writeCommandAcceptedinterrupt_session
create_sessionread + writeCommandAcceptedcreate_session
list_machinesreadMachine[]list_machines
list_harness_capabilitiesreadHarnessCapabilities[]list_harness_capabilities
get_usagereadUsageReportget_usage
get_pendingreadPendingInteractionget_pending
respond_to_pendingread + writeCommandAcceptedrespond_pending
list_pendingreadPendingListItem[]list_actionable_pending
respond_to_pending_by_idread + writeCommandCompletedrespond_actionable_pending
get_transcriptreadTranscriptPageget_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.

FieldTypeRequirementDescription
errorstringrequiredA human-readable failure reason.
{
  "error": "Session not found"
}

Health

FieldTypeRequirementDescription
okbooleanrequiredTrue when the process serves traffic.
{
  "ok": true
}

MachineStatus

Relay connection state of a paired runner.

Machine

One paired local runner.

FieldTypeRequirementDescription
idstringrequiredStable machine identifier. It is the first segment of every session ID.
namestringrequiredDisplay name that the runner reported during pairing.
owner_account_idstringrequiredAccount that owns the runner. The server always overwrites a reported value with the token account.
last_seen_atstringrequiredTime of the last relay message or heartbeat. (format date-time)
statusMachineStatusrequiredRelay connection state of a paired runner.
harnessesstring[]optionalHarness 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.

FieldTypeRequirementDescription
provider_idstringrequiredHarness model provider ID.
model_idstringrequiredHarness model ID.

ApprovalScope

How long the harness keeps an approval decision.

ApprovalResolution

Approval

A tool call that waits for a human decision.

FieldTypeRequirementDescription
type"approval"requiredDiscriminator.
idstringrequiredExact approval ID. Send it back as pending_id.
session_idstringrequiredNamespaced session ID.
tool_namestringrequiredTool that the harness wants to run.
tool_input_summarystringrequiredShort, elided description of the tool input.
commandstringoptionalExact command reported separately by the local harness adapter.
descriptionstringoptionalHuman-readable explanation reported by the local harness adapter.
sourcestringoptionalOpen-source CLI adapter or transport that observed the interaction.
response_modestring (remote · local)optionalWhether this exact interaction has a live remote response handler.
requested_atstringrequiredTime the harness raised the approval. (format date-time)
read_onlybooleanoptionalTrue when the native response window has closed and the item can only be answered locally.
expires_atstringoptionalLast instant at which the native harness accepts a remote response. (format date-time)
resolved_atstringoptionalTime the approval was answered. (format date-time)
resolutionApprovalResolutionoptional
{
  "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.

FieldTypeRequirementDescription
type"question"requiredDiscriminator.
idstringrequiredExact question ID. Send it back as pending_id.
session_idstringrequiredNamespaced session ID.
promptstringrequiredQuestion text shown to the user.
headerstringoptionalShort harness-provided label.
optionsstring[]optionalSuggested answers, when the harness offers a closed set.
option_detailsobject[]optionalSuggested answers with optional descriptions.
sourcestringoptionalOpen-source CLI adapter or transport that observed the interaction.
response_modestring (remote · local)optionalWhether this exact interaction has a live remote response handler.
read_onlybooleanoptionalTrue when the native response window has closed and the item can only be answered locally.
expires_atstringoptionalLast instant at which the native harness accepts a remote response. (format date-time)
requested_atstringrequiredTime the harness raised the question. (format date-time)
resolved_atstringoptionalTime the question was answered. (format date-time)
responsestringoptionalAnswer 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.

FieldTypeRequirementDescription
idstringrequiredNamespaced ID: {machine_id}/{harness_type}/{local_session_id}.
machine_idstringrequiredMachine that owns the session.
harness_typestringrequiredHarness that runs the session, for example opencode or claude-code.
cwdstringrequiredWorking directory on the local machine.
titlestringoptionalShort human-readable title reported by the harness. It can be absent when the harness has no title.
modelSessionModeloptionalModel identity reported by the harness.
agentstringoptionalHarness-specific agent, mode, or persona name.
read_onlybooleanoptionalTrue when Rivetplane can observe this session but the adapter cannot control it.
metadataobjectoptionalOptional safe, harness-specific metadata. Consumers must ignore unknown keys.
statusSessionStatusrequiredReported by the runner. The server never infers a state.
created_atstringrequiredTime the runner first reported the session. (format date-time)
last_activity_atstringrequiredTime of the most recent reported activity. (format date-time)
pendingApproval | Question | nullrequiredThe 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

FieldTypeRequirementDescription
pendingApproval | Question | nullrequiredThe 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

FieldTypeRequirementDescription
pendingPendingInteractionrequiredThe one interaction a session currently waits on.
session_idstringrequiredSession that owns the interaction.
machine_idstringrequiredRunner that owns the session.
harness_typestringrequiredHarness adapter type.
cwdstringrequiredSession working directory.
titlestringoptionalHarness-reported session title, when available.
modelSessionModeloptionalModel identity reported by the harness.
agentstringoptionalHarness-specific agent or mode, when available.
read_onlybooleanoptionalTrue when the session is observation-only.
actionablebooleanrequiredTrue when the online adapter reports response support.
reasonstringoptionalWhy an included item is not actionable.
reason_codestringoptionalStable 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.

FieldTypeRequirementDescription
idstringrequiredEvent ID that is unique inside the session.
session_idstringrequiredNamespaced session ID.
seqintegerrequiredMonotonic sequence number. Use it to order and to resume. (min 1)
tsstringrequiredTime the event happened on the machine. (format date-time)
typeTranscriptEventTyperequiredPayload discriminator of a transcript event.
payloaduser_message | agent_message | tool_call | tool_result | permission_request | permission_response | status_changerequiredType-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.

FieldTypeRequirementDescription
eventsTranscriptEvent[]requiredEvents in ascending sequence order.
next_cursorstring | nullrequiredPass 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.

FieldTypeRequirementDescription
provider_idstringrequiredProvider identifier reported by the harness.
model_idstringrequiredModel identifier reported by the harness.
namestringrequiredDisplay name.
statusstring (alpha · beta · deprecated · active)optionalLifecycle state reported by the harness.
context_limitintegeroptionalMaximum input tokens.
output_limitintegeroptionalMaximum output tokens.

HarnessCapabilities

What one harness on one machine can do right now.

FieldTypeRequirementDescription
machine_idstringrequiredMachine that reported the capabilities.
harness_typestringrequiredHarness type.
can_create_sessionbooleanrequiredTrue when the harness accepts remote session creation.
can_send_messagebooleanoptionalTrue when the adapter relays user messages. A discovery-only adapter omits it or reports false.
can_interruptbooleanoptionalTrue when the adapter can cancel the active turn.
can_respond_to_approvalbooleanoptionalTrue when the adapter can answer an approval.
can_respond_to_questionbooleanoptionalTrue when the adapter can answer a question.
directoriesstring[]requiredDirectories the harness allows. Session creation rejects any other value.
modelsHarnessModel[]requiredModels the caller may select for a new session.
default_modelobjectoptionalModel the harness uses when the caller has no preference.
reported_atstringrequiredTime 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.

FieldTypeRequirementDescription
inputinteger | nullrequired
outputinteger | nullrequired
reasoninginteger | nullrequired
cache_readinteger | nullrequired
cache_writeinteger | nullrequired
totalinteger | nullrequired

UsageCostSummary

Cost metadata, not an invoice. Any estimate makes status estimated. Partial coverage means some matching samples had no cost.

FieldTypeRequirementDescription
statusstring (reported · estimated · unavailable)requiredreported, estimated, or unavailable.
coveragestring (complete · partial · none)requiredWhether all, some, or no matching samples supplied cost.
priced_samplesintegerrequiredSamples with a cost value.
unavailable_samplesintegerrequiredSamples without a cost value.
amountnumberoptionalSum when one currency is present.
currencystringoptionalCurrency of amount.
by_currencyobject[]optionalSeparate sums when multiple currencies are present.

UsageAggregate

FieldTypeRequirementDescription
tokensUsageTokenCountsrequiredNormalized incremental token contributions. Null means that no matching source reported the counter.
costUsageCostSummaryrequiredCost metadata, not an invoice. Any estimate makes status estimated. Partial coverage means some matching samples had no cost.

UsageBreakdown

FieldTypeRequirementDescription
keystringrequiredGroup key.
tokensUsageTokenCountsrequiredNormalized incremental token contributions. Null means that no matching source reported the counter.
costUsageCostSummaryrequiredCost metadata, not an invoice. Any estimate makes status estimated. Partial coverage means some matching samples had no cost.

UsageContext

FieldTypeRequirementDescription
machine_idstringrequiredMachine that reported context.
session_idstring | nulloptional
harnessstringrequiredHarness that reported context.
providerstringoptionalModel provider, when known.
modelstringoptionalModel identifier, when known.
timestampstringrequiredTime of the latest context sample. (format date-time)
window_sizeintegerrequiredMaximum context tokens. (min 1)
used_tokensinteger | nulloptional

UsageQuotaWindow

FieldTypeRequirementDescription
namestringrequiredSource-defined quota window name.
used_percentnumber | nulloptional
remainingnumber | nulloptional
limitnumber | nulloptional
resets_atstringoptionalTime when this quota window resets. (format date-time)

UsageQuota

FieldTypeRequirementDescription
machine_idstringrequiredMachine that reported quota.
session_idstring | nulloptional
harnessstringrequiredHarness that reported quota.
providerstringoptionalProvider, when known.
modelstringoptionalModel, when known.
timestampstringrequiredTime of the latest quota sample. (format date-time)
windowsUsageQuotaWindow[]requiredLatest source-reported quota windows.

UsageReport

Append-only usage aggregated for the requested range. Missing values stay null or unavailable.

FieldTypeRequirementDescription
rangeobjectrequiredInclusive aggregation range.
totalsUsageAggregaterequired
breakdownsobjectrequiredTotals grouped by available identity fields.
contextUsageContext[]requiredLatest context state for each matching identity.
quotaUsageQuota[]requiredLatest quota state for each matching identity.
samples_countintegerrequiredUnique 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

FieldTypeRequirementDescription
cwdstringrequiredWorking directory. It must appear in the harness capability report.
titlestringoptionalOptional session title.
modelobjectrequiredModel 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

FieldTypeRequirementDescription
textstringrequiredMessage text. It must not be empty or only whitespace. (min length 1)
{
  "text": "Continue with the migration plan."
}

RespondToPendingRequest

FieldTypeRequirementDescription
pending_idstringrequiredExact ID of the active interaction. A stale ID returns 409.
responsestringrequiredapprove, deny, or the free text answer to a question.
scopeApprovalScopeoptionalHow long the harness keeps an approval decision.
{
  "pending_id": "per_01",
  "response": "approve",
  "scope": "once"
}

RespondByPendingIdRequest

FieldTypeRequirementDescription
responsestringrequiredapprove, deny, or the free text answer to a question.
scopeApprovalScopeoptionalHow long the harness keeps an approval decision.

CommandCompleted

FieldTypeRequirementDescription
command_idstringrequiredCommand ID reported by the runner.
completedtruerequiredThe runner reported success.

CommandAccepted

The control plane relayed a command to the runner. It does not mean the harness finished the work.

FieldTypeRequirementDescription
command_idstringrequiredCorrelate this with command_sent and command_result events.
acceptedtruerequiredAlways true.
{
  "command_id": "9b1f7a1e-6c5b-4a11-8e6f-2d0c9a4f31bb",
  "accepted": true
}

MachineRetired

FieldTypeRequirementDescription
retiredtruerequiredAlways true.
{
  "retired": true
}

MachineTokenRequest

FieldTypeRequirementDescription
codestringrequiredAuthorization code from the loopback callback.
code_verifierstringrequiredPKCE verifier for the S256 challenge.
redirect_uristringrequiredThe exact loopback callback used in the authorization request.
{
  "code": "1kQ7yq3fJ2Xo9m8vTn0pQ5rW6sD1aZ4c",
  "code_verifier": "b3T3wM9dLp1sV2xY7hR4nJ6kF8gC0uQ5",
  "redirect_uri": "http://127.0.0.1:45678/callback"
}

MachineTokenResponse

FieldTypeRequirementDescription
access_tokenstringrequiredMachine bearer token. The server stores only its hash.
token_type"Bearer"required
machine_idstringrequiredStable machine ID for this device.
owner_account_idstringrequiredAccount that owns the machine.
{
  "access_token": "shown-once",
  "token_type": "Bearer",
  "machine_id": "runner-01",
  "owner_account_id": "account-01"
}

DeviceCodeRequest

FieldTypeRequirementDescription
device_namestringrequiredController name. (min length 1)
device_idstringrequiredStable controller identity. (format uuid)

DeviceCodeResponse

FieldTypeRequirementDescription
device_codestringrequiredSecret code used only at the token endpoint.
user_codestringrequiredShort code shown to the user.
verification_uristringrequiredPage where the user enters the code. The code is separate so it does not enter URL logs. (format uri)
expires_in600requiredGrant lifetime in seconds.
intervalintegerrequiredMinimum polling interval in seconds. (min 5)

DeviceTokenRequest

FieldTypeRequirementDescription
device_codestringrequiredSecret device code from the issue response.

ConsumerDevice

FieldTypeRequirementDescription
idstringrequiredServer device record. (format uuid)
device_idstringrequiredStable controller identity. (format uuid)
namestringrequiredController name.
scopesstring (sessions:list · sessions:read · transcripts:read · usage:read · messages:send · pending:respond)[]requiredFixed minimum consumer permissions.
created_atstringrequiredPairing time. (format date-time)
last_used_atstring | nullrequiredMost recent token use, or null.
revoked_atstring | nullrequiredRevocation time, or null.

DeviceTokenResponse

FieldTypeRequirementDescription
access_tokenstringrequiredConsumer bearer token. The server stores only its SHA-256 hash.
token_type"Bearer"requiredBearer token type.
expires_innullrequiredNull because revocation controls token lifetime.
scope"sessions:list sessions:read transcripts:read usage:read messages:send pending:respond"requiredSpace-separated permissions.
deviceConsumerDevicerequiredPaired controller record.

DeviceTokenError

FieldTypeRequirementDescription
errorstring (authorization_pending · slow_down · expired_token · access_denied · invalid_grant)requiredDevice authorization state.
intervalintegeroptionalNew minimum polling interval after slow_down.

Revoked

FieldTypeRequirementDescription
revokedtruerequiredAlways true.

StripeWebhookAck

FieldTypeRequirementDescription
receivedtruerequired
{
  "received": true
}

JsonRpcRequest

MCP Streamable HTTP request.

FieldTypeRequirementDescription
jsonrpc"2.0"required
idstring | integer | nulloptionalOmit it only for notifications.
methodstringrequiredinitialize, ping, tools/list, tools/call, resources/list, resources/templates/list, resources/read, or a notifications/* method.
paramsobjectoptionalMethod parameters.
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

JsonRpcResponse

FieldTypeRequirementDescription
jsonrpc"2.0"required
idstring | integer | nullrequiredEchoes the request ID.
resultobjectrequiredMethod 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"
}
StatusMeaningWhat to do
400Invalid body, cursor, model, or working directoryRead the capability report and correct the request.
401Missing or invalid bearer tokenSend a current token in the Authorization header.
403The token cannot access this account, machine, or session, or it lacks the scopeUse a token of the owning account with the required scope.
404Unknown machine, session, MCP method, tool, or resourceList the parent collection again. The runner may have been retired.
409Runner offline, no active interaction, stale pending_id, or the harness cannot create sessionsRe-read state and retry with current values.
503Stripe Billing is not configuredExpected 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.