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.
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 unauthorizedto 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 answers426 cli_update_requiredwithminVersion. 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, is400 bad_request. A204has no body. - Ids. Control-plane routes take the box's
id, never its name;GET /api/v1/boxesmaps names to ids. Snapshots aresnp_…, environmentsenv_…, secretssec_…, grantssgr_…, operationsdop_…. An unknown, malformed, or foreign id is404 not_found, with nothing written. - 202 means in progress. Creating, waking, pausing, resizing, moving, forking, restoring and deleting a box answer
202with the row as written:desiredStatehas moved andobservedStatefollows over seconds to minutes. PollGET /api/v1/boxes/{id}, orGET /api/v1/operations/{id}for a delete. Setting writes answer200or201. - Errors. Codes are stable words; messages are for people. Refusals that need numbers carry them:
insufficient_balancehasbalanceMicros,requiredMicros,burnMicrosPerHour;snapshot_limithaslimit;box_not_runninghasstate. - Idempotency. No route takes an idempotency key. A
POST /api/v1/boxesretried after a lost response creates a second box; give it anameand the retry answers409 name_takeninstead. Deletes are idempotent, and wake or suspend on a box already in that state is a202no-op. - Limits. The control plane's limits are counts:
409 box_limit,429 snapshot_limit,402 secret_limit,429 customer_session_limiton the CLI's tunnel sessions. The edge admits 8 concurrent requests and 20 per second per token and per box, then answers429 rate_limitedwithRetry-After(Limits). - Times and money. Timestamps are RFC 3339 UTC. Balances are in micro-dollars (
1000000is $1). - Stability. This API is what the CLI and the dashboard use, and the box object is the one
prized … --jsonprints. Fields and routes are added under/v1without 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
| Route | Answer |
|---|---|
GET /api/v1/me | customer (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
| Route | Body | Answer |
|---|---|---|
GET /api/v1/boxes | 200 {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}/wake | 202 {box}, desiredState: "running" | |
POST /api/v1/boxes/{id}/suspend | 202 {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}/metrics | query 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-at | query 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.
| HTTP | error | When |
|---|---|---|
| 400 | bad_request, invalid_name, invalid_template, invalid_tier, invalid_env, invalid_auto_pause, invalid_pause_at, invalid_auto_snapshot, environment_too_large | The body is off; message names the window or the rule |
| 400 | resize_rejected, move_rejected | A box mid-transition, a fifth move in a day, a region the box cannot use |
| 403 | restore_tier_not_allowed | The size is off your plan or its disk cannot hold the snapshot |
| 403 | policy_denied, not_owner | A workspace policy refused a member (rule is memberSpendCapUsd, memberBoxLimit, memberMaxTier, autoPauseFloorMin or contractor_restricted); a quiet-hours exemption from anyone but the owner |
| 404 | not_found | No such box, snapshot, template or environment in this workspace, or a box a contractor does not own |
| 409 | box_limit, name_taken, insufficient_balance, tier_unavailable, workspace_closing, wake_rejected, snapshot_in_progress, box_not_running | In 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
| Route | Answer |
|---|---|
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
| Route | Body | Answer |
|---|---|---|
GET /api/v1/snapshots | query transient=1 to include fork copies | 200 {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}/template | 200 {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
| Route | What it does |
|---|---|
GET /api/v1/environments | 200 {environments} |
POST /api/v1/environments | Create; 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}/default | Make it the default for new boxes |
POST /api/v1/environments/{id}/toggles | The 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}/upgrade | Pin 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).
| Route | Body | Answer |
|---|---|---|
GET /api/v1/secrets | query box={id} for only the ones that box may mount | 200 {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 it | 201 {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}/usage | 200 {uses}: [{boxId, hostname, host, firstAt, lastAt, count}], up to 1,000 rows | |
GET /api/v1/boxes/{id}/secrets | 200 {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 step | 201 {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.
| HTTP | error | When |
|---|---|---|
| 400 | bad_request, invalid_name, invalid_host, invalid_env_name | The body is off |
| 402 | secret_limit | The workspace's secret count is full |
| 403 | secret_not_granted, not_allowed | A 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 |
| 404 | not_found, member_not_found | No such secret, grant or box (a terminated box included); a grant to a user id or email that is not a current member |
| 409 | name_taken, env_name_taken, box_restricted | A secret with that name, a mount on that variable, a box that is safe for third parties |
Policies
| Route | Answer |
|---|---|
GET /api/v1/workspace/policies | 200 {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
| Route | Body or query | Answer |
|---|---|---|
GET /api/v1/audit | query 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=csv | the same filters | text/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.
| HTTP | error | When |
|---|---|---|
| 400 | invalid_time, invalid_kind, invalid_box, invalid_actor, invalid_origin, invalid_cursor, invalid_limit, invalid_format | The named query parameter is off; message says the shape |
| 400 | invalid_kind, bad_request | A report of a kind clients may not claim, or a body that is off |
| 403 | not_allowed | A contractor reading, or a monitor ticket reporting |
| 404 | not_found | A report naming another workspace's box, or a ticket naming a box other than its own |
Team
| Route | Answer |
|---|---|
GET /api/v1/team/status | 200 {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
| Route | Body or query | Answer |
|---|---|---|
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: true | the same | 200 exec.started: processId, pid, cwd, startedAt, logPath |
GET /exec | 200 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
| Route | Body or query | Answer |
|---|---|---|
GET /files | query path | 200 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 /download | query path | The 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 /upload | query path, mode?; the raw file as the body | 200 file.uploaded: path, size |
Size caps are on Limits: Commands and files.
Prompts and events
| Route | Body or query | Answer |
|---|---|---|
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 /prompts | 200 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}/events | query 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=1 | or Accept: text/event-stream | Server-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}/interrupt | 200 prompt.interrupted: id, wasRunning, signal | |
POST /prompts/interrupt | The 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
| Route | Body | Answer |
|---|---|---|
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:
| HTTP | error.code | Meaning |
|---|---|---|
| 401 | unauthorized | No bearer, a token the control plane rejected, or a credential of another class (a monitor ticket, a phone password) |
| 404 | not_found | No such box on this account, process, run, path (or parent directory), or route |
| 405 | method_not_allowed | A known route with the wrong method; Allow lists the right ones |
| 409 | box_not_running | Any route but POST /exec and POST /prompts on a suspended box; the message says how to wake it |
| 400 | invalid_request | The 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 |
| 400 | invalid_timeout | timeoutSeconds outside 1 to 600 |
| 400 | invalid_path | A path with a NUL or newline byte, a directory where a file was expected, or no path |
| 403 | permission_denied | The box's account cannot read or write that path |
| 413 | payload_too_large | Over the inline (1 MiB) or transfer (1 GiB) cap |
| 429 | rate_limited | Too many requests for this token or box; Retry-After says when |
| 502 | box_unreachable | The box could not be reached, or did not wake in time |
| 502 | exec_failed | The ssh session or the on-box script failed; the message carries the box's own words |
| 5xx | control_plane_unavailable | The control plane could not be reached to check the token |
The prompt routes add:
| HTTP | error.code | Meaning |
|---|---|---|
| 400 | invalid_provider | provider is not claude or codex |
| 400 | prompt_required | prompt is missing or blank |
| 409 | prompt_in_progress | A run is in progress; the message names it. Queue behind it, or interrupt it |
| 409 | provider_not_signed_in | The provider has no sign-in on the box; run prized agents handoff <provider> |
| 409 | provider_not_installed | The provider's CLI is not on the box's login PATH |
| 409 | no_session_to_continue | continue: true with no earlier run of the provider that recorded a session |
Examples
Create a box and wait for it to run:
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; doneRun a command on it, upload a file, download a directory:
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.tarPrompt 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):
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:
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; doneNot yet
- No SDK and no OpenAPI document. The routes above with
curlor your language's HTTP client are the whole interface; the CLI's--jsonoutput is the other machine interface (CLI: For agents and scripts). - No webhooks. Poll
GET /api/v1/boxes/{id}orGET /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?
CLI
Every prized command, flag, and exit code, on macOS, Linux, and Windows. Add --help to any command; every non-interactive command also takes --json and prints exactly one JSON object.
Dashboard
Every screen in the signed-in app at prized.dev/dashboard, what each control does, and where a setting lives. The feature pages explain the behaviour; this one says where to click.