Prized docs

API reference.

Every HTTP route a CLI token can call. The control plane at api.prized.dev/api/v1 manages boxes, snapshots, environments, secrets, policies, the audit log and team status; the edge runs commands, moves files, prompts the box's agent and stops its desktop. No SDK and no OpenAPI document yet; this page is the contract.

Two hosts, one token

The control plane, https://api.prized.dev/api/v1, manages boxes, snapshots, environments, secrets, operations, the audit log, policies, team status and your account; it takes a box's id (a uuid) and answers errors as {"error": "code"}, sometimes with message. The edge, $EDGE/v1/box/{box}/, runs commands, moves files, prompts the agent and stops the desktop; it takes a box's name or id and answers errors as {"ok": false, "error": {"code", "message"}}. Both take the same bearer; read the edge's address from edge.url in GET /api/v1/me rather than assuming one.

Terminal
export PRIZED_TOKEN=dcp_…
API=https://api.prized.dev/api/v1
AUTH="Authorization: Bearer $PRIZED_TOKEN"
EDGE=$(curl -s "$API/me" -H "$AUTH" | jq -r .edge.url)

Authentication

Every request carries Authorization: Bearer dcp_…, a CLI token from Dashboard → Workspace → CLI tokens (prized login --token dcp_… signs a machine in with one). Mint one per script or agent and name it, so it can be retired on its own; revoking takes effect on its next request (401) and closes the tunnels that device opened (Workspaces: CLI tokens).

  • What a token can do. It acts as you in the workspace it was minted in: every box, snapshot, environment and secret there (a contractor's token reaches only the boxes they own and no secrets). Minting credentials, membership and roles, policies, the audit export, billing, and closing the workspace are dashboard-only; those routes answer 401 unauthorized to any bearer, so a leaked token cannot widen itself.
  • Other credentials. The dashboard's short-lived terminal tickets are accepted on the box read, wake and connect routes and on the edge's exec, files and prompt routes, for their one box only; monitor tickets and phone passwords are refused everywhere.
  • The version header. The CLI sends X-Prized-CLI-Version; one below the minimum answers 426 cli_update_required with minVersion. Scripts should not send it.

Conventions

  • JSON in, JSON out. Send Content-Type: application/json. Bodies are capped at 64 KiB (128 KiB on the secrets routes; 2 MiB on environment create, patch, files and repos); over the cap, or not JSON, is 400 bad_request. A 204 has no body.
  • Ids. Control-plane routes take the box's id, never its name; GET /api/v1/boxes maps names to ids. Snapshots are snp_…, environments env_…, secrets sec_…, grants sgr_…, operations dop_…. An unknown, malformed, or foreign id is 404 not_found, with nothing written.
  • 202 means in progress. Creating, waking, pausing, resizing, moving, forking, restoring and deleting a box answer 202 with the row as written: desiredState has moved and observedState follows over seconds to minutes. Poll GET /api/v1/boxes/{id}, or GET /api/v1/operations/{id} for a delete. Setting writes answer 200 or 201.
  • Errors. Codes are stable words; messages are for people. Refusals that need numbers carry them: insufficient_balance has balanceMicros, requiredMicros, burnMicrosPerHour; snapshot_limit has limit; box_not_running has state.
  • Idempotency. No route takes an idempotency key. A POST /api/v1/boxes retried after a lost response creates a second box; give it a name and the retry answers 409 name_taken instead. Deletes are idempotent, and wake or suspend on a box already in that state is a 202 no-op.
  • Limits. The control plane's limits are counts: 409 box_limit, 429 snapshot_limit, 402 secret_limit, 429 customer_session_limit on the CLI's tunnel sessions. The edge admits 8 concurrent requests and 20 per second per token and per box, then answers 429 rate_limited with Retry-After (Limits).
  • Times and money. Timestamps are RFC 3339 UTC. Balances are in micro-dollars (1000000 is $1).
  • Stability. This API is what the CLI and the dashboard use, and the box object is the one prized --json prints. Fields and routes are added under /v1 without notice; ignore fields you do not know. The CLI's own tunnel and housekeeping routes are not a scripting surface and are not listed.

Account

RouteAnswer
GET /api/v1/mecustomer (id, email, name, githubLogin, status, onboardingStep, onboardingCompletedAt, createdAt), user (who the token acts as: email, name, role; null for a token with no user), workspace (id, name, zeroDataRetention, closingAt, purgeAfter), billing (balanceMicros, burnMicrosPerHour, runwayHours, warnBelowHours, planAmountUsd), boxes (every box, terminated included; a contractor's own), edge.url

customer.id is the workspace id. box (the oldest live box in boxes) is deprecated; read boxes.

Boxes

RouteBodyAnswer
GET /api/v1/boxes200 {boxes}, newest first, terminated ones included (filter on observedState)
POST /api/v1/boxes{name?, tier?, ttlMinutes?, autoPauseMin?, environment?, env?, restricted?, fromSnapshotId?, fromTemplate?}202 {box} with observedState: "requested"; poll until running
GET /api/v1/boxes/{id}200 {box, state, vitals, events, job, pausesSurvived, environment, envVars, restricted}
PATCH /api/v1/boxes/{id}{autoPauseMin?, ttlMinutes? | pauseAt?, autoSnapshotHours?, autoSnapshotKeep?, quietHoursExempt?} or, alone, {env}200 {box}
DELETE /api/v1/boxes/{id}202 {box, operation}; see Operations
POST /api/v1/boxes/{id}/wake202 {box}, desiredState: "running"
POST /api/v1/boxes/{id}/suspend202 {box}, desiredState: "suspended"
POST /api/v1/boxes/{id}/resize{target_tier}202 {box} with the new tier; the box restarts
POST /api/v1/boxes/{id}/region{region} (us-west-2 or us-west-1)202 {box} with desiredRegion set; region changes when the move lands
POST /api/v1/boxes/{id}/fork{name?, tier?, keepSnapshot?}202 {box, snapshot}
POST /api/v1/boxes/{id}/template{name, description?}201 {snapshot}: a template of this box, taken now
GET /api/v1/boxes/{id}/metricsquery from, to (epoch seconds or …Z ISO 8601; default the last hour, clamped to 30 days)200 {v, res, from, to, buckets, series, fs, ifaces}; res is 30, 300 or 3600 seconds by span
GET /api/v1/boxes/{id}/procs-atquery t (required)200 {v, at, procs, ports}: the process and port snapshot nearest t, within five minutes, else 404

The box object is the one on CLI: The box object, plus autoSnapshotHours, autoSnapshotKeep, environmentId, environmentVersionNo, envVars ([{name, value}]), restricted, ownerUserId (the member who created it) and quietHoursExempt. desiredState is running, suspended or terminated; observedState is one of requested, provisioning, bootstrapping, running, suspending, suspended, waking, deep_sleep, resizing, migrating, degraded, provision_failed, terminating, terminated (Boxes: States).

Creating. name is a hostname (lowercase letters, digits and hyphens, starting and ending with a letter or digit, 1 to 32 characters; a fruit without one). tier is a size id (nano, micro, lite, flow, pro, max, ultra); without one the control plane picks what the balance runs comfortably (a Small while the workspace runs on the free credit alone, which also starts only nano through flow: the rest answer 409 tier_unavailable with allowed naming the sizes it does). A create on the free credit that omits autoPauseMin gets 60; null keeps auto-pause off. ttlMinutes (5 to 43200) is a pause deadline, autoPauseMin (30 to 10080, or null) the idle window. environment is an id or name, null for none, absent for your default; env is {NAME: "value"}; restricted makes the box safe for third parties (a contractor's box always is). fromSnapshotId or fromTemplate (one, not both) restores instead of booting fresh; tier may then be any size whose disk holds the snapshot.

The detail answer. events is the last 50 state changes as {id, fromState, toState, reason, actor, at}; job is the lifecycle change in flight (op, step, attempt, lastError, nextRetryAt) or null; vitals is the last heartbeat (cpu_pct, mem_pct, disk_pct, idle_for_sec, listeners_count, agent_version); environment is {id, name, versionNo, latestVersionNo} or null.

Patching takes one family per request: the pause settings and snapshot schedule together (autoPauseMin; ttlMinutes or pauseAt, null to clear, only one of the two; autoSnapshotHours 1 to 168 or null; autoSnapshotKeep 1 to 10), or {env} alone, which replaces the per-box vars (100 names, 64 KiB in all). A deadline needs a running box (409 box_not_running); clearing one always works. quietHoursExempt keeps the box running through quiet hours and is the owner's alone (403 not_owner); a member's autoPauseMin above the workspace's auto-pause floor is 403 policy_denied.

HTTPerrorWhen
400bad_request, invalid_name, invalid_template, invalid_tier, invalid_env, invalid_auto_pause, invalid_pause_at, invalid_auto_snapshot, environment_too_largeThe body is off; message names the window or the rule
400resize_rejected, move_rejectedA box mid-transition, a fifth move in a day, a region the box cannot use
403restore_tier_not_allowedThe size is off your plan or its disk cannot hold the snapshot
403policy_denied, not_ownerA workspace policy refused a member (rule is memberSpendCapUsd, memberBoxLimit, memberMaxTier, autoPauseFloorMin or contractor_restricted); a quiet-hours exemption from anyone but the owner
404not_foundNo such box, snapshot, template or environment in this workspace, or a box a contractor does not own
409box_limit, name_taken, insufficient_balance, tier_unavailable, workspace_closing, wake_rejected, snapshot_in_progress, box_not_runningIn order: the plan's box count (1 while the workspace runs on the free credit alone, with message and limit), a live box with that name, a balance short of four hours, a size the plan or the free credit refuses (message says which; allowed lists the free credit's sizes), a closing workspace, a wake the balance refuses, a template still being taken, a deadline on a paused box

Operations

RouteAnswer
GET /api/v1/operations/{id}200 {operation}: id, kind (box_delete, snapshot_delete), targetId, status, requestedAt, completedAt, error

A delete answers with one of these; status runs pending, processing, completed, or failed with error. Finished operations stay readable for 30 days. What a delete keeps and removes is on Data retention.

Snapshots

RouteBodyAnswer
GET /api/v1/snapshotsquery transient=1 to include fork copies200 {snapshots}, newest first
POST /api/v1/snapshots{boxId, name, description?}201 {snapshot} with status: "creating"; poll the list until available
DELETE /api/v1/snapshots/{id}202 {snapshot, operation}
POST /api/v1/snapshots/{id}/restore{name?, tier?}202 {box}, a new box from the snapshot
POST /api/v1/snapshots/{id}/template{name}200 {snapshot, previous}; previous is the snapshot that held the name, or null
DELETE /api/v1/snapshots/{id}/template200 {snapshot}, back to a plain snapshot

A snapshot is {id, name, description, status, kind, templateName, expiresAt, boxId, sourceBoxHostname, tier, region, sizeGb, error, createdAt} (status: creating, available, failed, deleting; kind: manual, template, fork, auto, final); other fields may disappear. Names are 1 to 64 characters of letters, digits, ., _ and -, starting and ending with a letter or digit. Forking and templating a box are under Boxes; what each kind means is on Snapshots.

Refusals: 409 snapshot_in_progress, 409 no_volume, 409 insufficient_balance, 409 name_taken, 409 box_limit, 409 snapshot_unavailable (a failed, deleting, or transient fork snapshot as a template), 429 snapshot_limit, 403 restore_tier_not_allowed. A POST /api/v1/boxes with fromTemplate is 404 when no template wears the name.

Environments

RouteWhat it does
GET /api/v1/environments200 {environments}
POST /api/v1/environmentsCreate; 201 {environment}
PATCH /api/v1/environments/{id}Change any field; a config change mints a version; 200 {environment}
DELETE /api/v1/environments/{id}204; boxes keep their version
POST /api/v1/environments/{id}/defaultMake it the default for new boxes
POST /api/v1/environments/{id}/togglesThe safe-for-third-parties switch and the three credential channels
POST /api/v1/environments/{id}/vars, DELETE …/vars/{name}Set or remove one env var
PUT /api/v1/environments/{id}/files, DELETE …/files?path=Set or remove one secret file
POST /api/v1/environments/{id}/repos, DELETE …/repos?repo=Add or remove one repo
POST /api/v1/environments/{id}/upgradePin live boxes to the latest version; 200 {upgraded, skipped, latestVersionNo}

Bodies, the environment object, limits and error codes are on Environments: API.

Secrets

Brokered credentials: the box sees a placeholder, and the broker substitutes the real value only in HTTPS requests to the allowed hosts (Credentials, Connectors).

RouteBodyAnswer
GET /api/v1/secretsquery box={id} for only the ones that box may mount200 {secrets}, never a value
POST /api/v1/secrets{name, value, description?, hosts?, grants?}grants is a list of {boxId}, {userId} or {email}, the scope the secret is born with, committed with it201 {secret}; 404 (a grant's box), 404 member_not_found
PATCH /api/v1/secrets/{id}{value?, description?, hosts?} (at least one)200 {secret}; a new value keeps the placeholder and stamps lastRotatedAt
DELETE /api/v1/secrets/{id}204; its mounts, grants and usage go with it
POST /api/v1/secrets/{id}/grants{boxId}, {userId} or {email} (one of the three)201 {grant}; an existing grant answers 201 again
DELETE /api/v1/secrets/{id}/grants/{grantId}204
GET /api/v1/secrets/{id}/usage200 {uses}: [{boxId, hostname, host, firstAt, lastAt, count}], up to 1,000 rows
GET /api/v1/boxes/{id}/secrets200 {mounts}
POST /api/v1/boxes/{id}/secrets{secretId, envName} to mount an existing secret, or {name, value, envName, hosts?, description?} to create and mount in one step201 {mount}
DELETE /api/v1/boxes/{id}/secrets/{mountId}204

A secret is {id, name, description, hosts, placeholder, mountCount, scope, grants, createdAt, updatedAt, lastRotatedAt}, where scope is workspace (no grants: every box whose owner is not a contractor may mount it) or granted (only the boxes named, and the boxes owned by the members named) and grants is [{id, boxId, hostname, userId, email, createdAt}]; a mount is {id, envName, secret: {id, name, placeholder, hosts}}. name is UPPER_SNAKE_CASE (up to 128 characters), value up to 16 KiB, hosts up to 32 entries, each a hostname or a *. wildcard; envName is a shell identifier. The grant rule is on Teams: Scoped secrets.

HTTPerrorWhen
400bad_request, invalid_name, invalid_host, invalid_env_nameThe body is off
402secret_limitThe workspace's secret count is full
403secret_not_granted, not_allowedA mount of a secret the box may not have (the create-and-mount form too; nothing is committed); a contractor on any secrets route or box mount
404not_found, member_not_foundNo such secret, grant or box (a terminated box included); a grant to a user id or email that is not a current member
409name_taken, env_name_taken, box_restrictedA secret with that name, a mount on that variable, a box that is safe for third parties

Policies

RouteAnswer
GET /api/v1/workspace/policies200 {policy, updatedBy, updatedAt}; owners and members; a contractor is 403 not_allowed

policy is {memberSpendCapUsd, memberBoxLimit, memberMaxTier, autoPauseFloorMin, quietHours, auditRetentionDays}, each null while off except auditRetentionDays (90 by default); quietHours is {start, end, days, allDayDays, timezone}. Changing them is PATCH /api/v1/workspace/policies from a dashboard session only: the body is the keys to change, null clears one (400 invalid_policy with field and message, 403 not_owner, 409 workspace_closing). A create, wake, resize or fork a policy refuses answers 403 policy_denied with rule and message. Bounds and what each rule does are on Teams: Policies.

Audit log

RouteBody or queryAnswer
GET /api/v1/auditquery since, until (RFC 3339 UTC or epoch seconds), kind (comma list), box (id), actor (user id), origin (server or client), before (a row id cursor), limit (1 to 1000, default 100)200 {events, nextBefore}, newest first; nextBefore is null on the last page
GET /api/v1/audit?format=csvthe same filterstext/csv as an attachment audit-YYYYMMDD-HHMMSS.csv, columns id,at,kind,origin,actor_kind,actor_user_id,actor_token_id,box_id,detail, every row of the range up to 50,000
POST /api/v1/audit{kind, boxId, detail?}201 {id}: a client report, from a CLI token or a terminal ticket

An event is {id, at, kind, origin, actorKind, actorUserId, actorTokenId, boxId, detail}; actorKind is user, cli, ticket, mobile or system. Owners and members read; a contractor is 403 not_allowed. Only exec, file.upload, file.download, prompt and desktop.start may be reported; detail takes 16 keys of up to 40 characters with strings of up to 512, the body 64 KiB, and a ticket may report only on its own box. Kinds, details and what a reported row means are on Teams: Audit log.

HTTPerrorWhen
400invalid_time, invalid_kind, invalid_box, invalid_actor, invalid_origin, invalid_cursor, invalid_limit, invalid_formatThe named query parameter is off; message says the shape
400invalid_kind, bad_requestA report of a kind clients may not claim, or a body that is off
403not_allowedA contractor reading, or a monitor ticket reporting
404not_foundA report naming another workspace's box, or a ticket naming a box other than its own

Team

RouteAnswer
GET /api/v1/team/status200 {now, cycle: {start, end}, scope, boxes, members, totals, recent}

scope is workspace, or own for a contractor (their boxes only, and members empty). A box is {id, hostname, tier, desiredState, observedState, ownerUserId, ownerName, ownerEmail, idleForSec, cpuPct, memPct, lastHeartbeatAt, sessions, lastPromptAt, lastExecAt, spendTodayMicros, spendCycleMicros} (idleForSec, cpuPct and memPct are null on a paused box); a member is {userId, name, email, role, boxes, sessions, spendCycleMicros, lastActiveAt}; totals is {spendTodayMicros, spendCycleMicros, running, sessions}; recent is the newest 50 audit events. What a session, today and the cycle mean is on Teams: Team activity.

The edge

Every route below is under $EDGE/v1/box/{box}/, where {box} is the box's name or id, with the same bearer; each request is checked against the control plane, so a revoked token dies on its next request. POST /exec and POST /prompts wake a suspended box; every other route answers 409 box_not_running instead. Paths are read the way prized cp reads them: relative paths and ~/… are under your home, absolute paths are taken as is, and every answer echoes the real path it touched.

Every successful answer carries ok: true and a type. A failed one is {"ok": false, "error": {"code", "message"}}, with the codes under Errors below; a known path with the wrong method is 405 with Allow, an unknown path 404 not_found, in the same envelope.

Commands

RouteBody or queryAnswer
POST /exec{command, cwd?, timeoutSeconds? (1 to 600, default 30), env? (up to 32), detached?}200 exec.result: exitCode, signal, stdout, stderr, stdoutTruncated, stderrTruncated, timedOut, startedAt, finishedAt, cwd
POST /exec with detached: truethe same200 exec.started: processId, pid, cwd, startedAt, logPath
GET /exec200 exec.list: processes[] of processId, pid, running, lost, exitCode, signal, startedAt, command
GET /exec/{processId}query tail (bytes; default 16384, max 1048576)200 exec.status: the list fields plus finishedAt, cwd, stdout, stderr, stdoutTruncated, stderrTruncated
DELETE /exec/{processId}200 exec.killed: processId, wasRunning, signal (TERM, or KILL when TERM was not enough)

The command runs under sh -c in your home (or cwd) as your box user. timedOut is set only when the time limit ended the command, so a command that exits 124 by itself is exitCode: 124, timedOut: false. A synchronous run that outgrows 1 MiB per stream keeps the last 1 MiB and sets the Truncated flag; use a detached run and /download on its log for the whole thing. The on-box process directory is on Run commands and files.

Files

RouteBody or queryAnswer
GET /filesquery path200 file.read: path, encoding (utf8 or base64), size, mode, content; over 1 MiB answers 413 and points at /download
PUT /files{path, content, encoding? (utf8 or base64), mode? ("0644")}200 file.written: path, size
GET /downloadquery pathThe file as application/octet-stream with Content-Length, or a directory as application/x-tar with X-Prized-Content: directory; X-Prized-Path carries the real path
PUT /uploadquery path, mode?; the raw file as the body200 file.uploaded: path, size

Size caps are on Limits: Commands and files.

Prompts and events

RouteBody or queryAnswer
POST /prompts{provider (claude or codex), prompt (up to 512 KiB), model?, reasoningEffort? (low, medium, high), cwd?, continue?, queue?, auto?}202 prompt.started: run; or 202 prompt.queued: queued, active
GET /prompts200 prompt.list: runs[] newest first (at most 50), active (the run in progress, or null)
GET /prompts/{runId}200 prompt.status: run with the full prompt, stderr (last 4 KiB), stderrTruncated
GET /prompts/{runId}/eventsquery after (a seq, default 0), limit (1 to 1000, default 200; a page also stops at 1 MiB)200 prompt.events: run, events[], next, finished; pass next as the following after
GET /prompts/{runId}/events?follow=1or Accept: text/event-streamServer-Sent Events: event: prompt per event, event: done with {run, next} when the run has ended, event: error with {code, message} if the box stops answering, a : keep-alive comment while nothing happens
POST /prompts/{runId}/interrupt200 prompt.interrupted: id, wasRunning, signal
POST /prompts/interruptThe same, for the run in progress

A run is {id, provider, model, reasoningEffort, auto, status (running, done, failed, interrupted), exitCode, pid, cwd, startedAt, finishedAt, sessionId, resumedFrom, parent, eventCount, prompt}; an event is {seq, at, type, text, tool: {name, input}, raw} (Prompt an agent remotely: JSON and JSON Lines). A stream holds one of the token's concurrent request slots until the run ends. Approvals, continuity, the queue and the run directory on the box are on Prompt an agent remotely.

Desktop

RouteBodyAnswer
POST /desktop/stop{mode?} (desktop, the default, or browser)200 {ok: true, box, mode}; never wakes a paused box

This one route predates the edge envelope: its errors are the bare {"error": "code"} form, with unauthorized (a dcp_ token only), not_found, bad_request, box_not_running, box_unreachable, desktop_stop_failed, resolve_failed and control_plane_unavailable. The desktop stream itself is a dashboard WebSocket; from a script use prized desktop --no-open, which prints a vnc:// address and the password (Desktop).

Errors

Every edge route answers a failure as {"ok": false, "error": {"code", "message"}} with one of these codes:

HTTPerror.codeMeaning
401unauthorizedNo bearer, a token the control plane rejected, or a credential of another class (a monitor ticket, a phone password)
404not_foundNo such box on this account, process, run, path (or parent directory), or route
405method_not_allowedA known route with the wrong method; Allow lists the right ones
409box_not_runningAny route but POST /exec and POST /prompts on a suspended box; the message says how to wake it
400invalid_requestThe body is not the documented shape: bad JSON, an empty command, a bad env name or mode, a bad model, a reasoningEffort outside low/medium/high, a newline in cwd
400invalid_timeouttimeoutSeconds outside 1 to 600
400invalid_pathA path with a NUL or newline byte, a directory where a file was expected, or no path
403permission_deniedThe box's account cannot read or write that path
413payload_too_largeOver the inline (1 MiB) or transfer (1 GiB) cap
429rate_limitedToo many requests for this token or box; Retry-After says when
502box_unreachableThe box could not be reached, or did not wake in time
502exec_failedThe ssh session or the on-box script failed; the message carries the box's own words
5xxcontrol_plane_unavailableThe control plane could not be reached to check the token

The prompt routes add:

HTTPerror.codeMeaning
400invalid_providerprovider is not claude or codex
400prompt_requiredprompt is missing or blank
409prompt_in_progressA run is in progress; the message names it. Queue behind it, or interrupt it
409provider_not_signed_inThe provider has no sign-in on the box; run prized agents handoff <provider>
409provider_not_installedThe provider's CLI is not on the box's login PATH
409no_session_to_continuecontinue: true with no earlier run of the provider that recorded a session

Examples

Create a box and wait for it to run:

Terminal
BOX=$(curl -s -X POST "$API/boxes" -H "$AUTH" -H "Content-Type: application/json" -d '{"name":"ci-42","tier":"flow","ttlMinutes":120}' | jq -r .box.id)
until [ "$(curl -s "$API/boxes/$BOX" -H "$AUTH" | jq -r .box.observedState)" = running ]; do sleep 5; done

Run a command on it, upload a file, download a directory:

Terminal
curl -s -X POST "$EDGE/v1/box/ci-42/exec" -H "$AUTH" -H "Content-Type: application/json" -d '{"command":"make test","cwd":"app","timeoutSeconds":300}' | jq '{exitCode, timedOut}'
curl -s -X PUT "$EDGE/v1/box/ci-42/upload?path=data/train.bin" -H "$AUTH" --data-binary @train.bin
curl -s "$EDGE/v1/box/ci-42/download?path=app/dist" -H "$AUTH" -o dist.tar

Prompt the agent and follow it (a box with a run in progress answers prompt.queued with no run; pass queue: true, or interrupt it first):

Terminal
RUN=$(curl -s -X POST "$EDGE/v1/box/ci-42/prompts" -H "$AUTH" -H "Content-Type: application/json" -d '{"provider":"claude","prompt":"fix the failing tests in app/ and run them again","cwd":"app","auto":true}' | jq -r .run.id)
curl -sN "$EDGE/v1/box/ci-42/prompts/$RUN/events" -H "$AUTH" -H "Accept: text/event-stream"

Delete the box and wait for the operation:

Terminal
OP=$(curl -s -X DELETE "$API/boxes/$BOX" -H "$AUTH" | jq -r .operation.id)
until [ "$(curl -s "$API/operations/$OP" -H "$AUTH" | jq -r .operation.status)" = completed ]; do sleep 5; done

Not yet

  • No SDK and no OpenAPI document. The routes above with curl or your language's HTTP client are the whole interface; the CLI's --json output is the other machine interface (CLI: For agents and scripts).
  • No webhooks. Poll GET /api/v1/boxes/{id} or GET /api/v1/operations/{id}; the push channels are the prompt event stream and the audit export, which carries audit rows only.
  • No API keys apart from CLI tokens. A token is a signed-in machine with your full standing in the workspace; there is no read-only or per-box token. Mint one per script and revoke it when the script is retired.
  • No idempotency keys, see Conventions.

Something unclear or out of date?

On this page