HTTP API
Every endpoint of the Drums gateway at https://beta.drums.run/gw, with its scope, parameters, response, errors and a verified example.
The gateway serves the drumsd /v1 API to one workspace: every path below is under https://beta.drums.run/gw. The Python SDK is a thin client of exactly these routes, so anything the SDK does, any HTTP client can do. Methods, paths and scopes on this page come from the gateway's own route and scope tables; response fields come from the OpenAPI document the gateway serves at /gw/openapi.json.
Conventions
- Authentication:
Authorization: Bearer <API key>on every request exceptGET /gw/healthz(see Authentication). - Bodies are JSON (
Content-Type: application/json), at most 4 MiB. Unknown fields are refused with 400. - Ids: computers and saved states are UUIDs (the ids the console shows); base images keep their names (
osworld-ubuntu); tasks are OSWorld task ids. - Records are the runtime's own, without host internals (ports, process ids, host paths, slot numbers). They may carry more fields than listed here.
- Errors are
{"error": "<message>", "code": "<code>", "request_id": "gw_...", "details": {...}}and every reply has anX-Request-Idheader. See Errors. - Names: a computer is a slot here, a saved state a checkpoint, save is capture and fork is branch.
- Examples assume
DRUMS_URLandDRUMS_TOKENexported as in the quickstart andjqinstalled.COMPUTER_ID,SNAPSHOT_ID,RUN_IDandKEY_IDare ids from earlier calls;TASK_SET_IDandTEMPLATE_VERSION_IDare ids the console shows under Runs and Templates.
Endpoints
| Endpoint | Scope | SDK |
|---|---|---|
| GET/v1/health | any valid key | client.health |
| GET/v1/metrics | usage:read | client.metrics |
| POST/v1/metrics/reset | computers:write | client.reset_metrics |
| GET/v1/tasks | computers:read or runs:read | client.tasks |
| GET/v1/tasks/{id} | computers:read or runs:read | client.task |
| GET/v1/checkpoints | computers:read | client.images |
| GET/v1/checkpoints/{id} | computers:read | — |
| DELETE/v1/checkpoints/{id} | computers:write | client.delete_state |
| GET/v1/slots | computers:read | client.envs |
| POST/v1/slots | computers:write | client.create |
| GET/v1/slots/{id} | computers:read | env.refresh |
| DELETE/v1/slots/{id} | computers:write | env.close |
| POST/v1/slots/{id}/step | computers:write | env.step |
| GET/v1/slots/{id}/screenshot | computers:read | env.screenshot |
| GET/v1/slots/{id}/gates | computers:read | env.gates |
| POST/v1/slots/{id}/verify | computers:write | env.verify |
| POST/v1/slots/{id}/reset | computers:write | env.reset |
| POST/v1/slots/{id}/task | computers:write | env.load_task |
| GET/v1/slots/{id}/metrics | computers:read | env.metrics |
| POST/v1/slots/{id}/capture | computers:write | env.save |
| POST/v1/slots/{id}/branch | computers:write | env.fork |
| POST/v1/slots/{id}/quiescent | computers:write | env.wait_quiescent |
| POST/v1/slots/{id}/quarantine | computers:write | env.quarantine |
| POST/v1/slots/{id}/release | computers:write | env.release |
| GET/v1/runs | runs:read | — |
| POST/v1/runs | runs:write | — |
| GET/v1/runs/{id} | runs:read | — |
| GET/v1/api-keys | any valid key | — |
| POST/v1/api-keys | any valid key | — |
| DELETE/v1/api-keys/{id} | any valid key | — |
| GET/v1/audit | audit:read | — |
Unauthenticated routes
| Route | Reply |
|---|---|
GET /gw/healthz | {"ok":true}: the gateway process is up |
GET /gw/openapi.json | The OpenAPI 3.1 document of the routes below |
Common errors
Any authenticated route can answer with these; each endpoint below lists only its own.
| 401unauthenticated | The key is missing, malformed, unknown, revoked or expired, or its owner is no longer an active member, or the workspace is suspended. One reply for all of them. |
| 403forbidden | The key's effective scopes (key scopes intersected with the owner's current role) do not include the route's scope. |
| 404not_found | No such route (also for a method the path does not support), or the id is not a computer or saved state of your workspace on this host. Ids from other workspaces are 404, never 403. |
| 429rate_limited | More than 20 requests per second per key (burst 60), or more than 20 failed authentications from one IP address. Wait for Retry-After seconds. |
| 503retryable_infrastructure_failure | The gateway could not reach the runtime or its database. Safe to retry a GET; for a POST check the resource first (the outcome may be unknown). |
Host
Health
The runtime version, the images you can launch, per-desktop limits, lease defaults and the host's live capacity.
Python SDK: client.health()
- Needs a valid key but no scope.
checkpointslists only the base images of template versions your workspace may launch on this host; saved states are not listed here.capacity.livecounts live desktops on the host;capacity.maxis the host's desktop limit. The host serves one workspace.
{ok, version, criu: {sha256}, image_sha256, checkpoints: [{id, stale_reason}], limits, leases: {defaults}, capacity: {live, max}}
curl -s "$DRUMS_URL/v1/health" -H "Authorization: Bearer $DRUMS_TOKEN"{"ok":true,"version":"0.4.2","criu":{"sha256":"93403b4a…"},"image_sha256":"6bf667a8…",
"checkpoints":[{"id":"osworld-ubuntu","stale_reason":null}],
"limits":{"max_concurrent_restores":4,"memory_mib":4096,"swap_mib":0,"vcpus":4},
"leases":{"defaults":{"env_idle_timeout_s":1800,"env_ttl_s":null,"reap_interval_s":15,"state_idle_timeout_s":1800,"state_ttl_s":null}},
"capacity":{"live":0,"max":10}}Host metrics
The runtime's aggregate metrics for the current window: operation latencies, episodes by verdict, failures by cause, lease reaps and measured compute cost.
Python SDK: client.metrics()
- The host serves one workspace, so these are your workspace's numbers. The gateway adds
cost_label: "measured compute cost (not billed)". - The window starts when the runtime started or at the last metrics reset, and its wall time includes idle time.
{usage: {episodes, ops, failures_by_cause, …}, leases_reaped, slots_by_state, cost: {…}, cost_note, definitions, cost_label}
Runtime notes (drumsd)
Current window: per-op latency (n, failed, mean/p50/p95, total s) for create/reset/fork/capture/step/verify/task_setup; episodes by verdict (counted once when the verdict resolves; ended_pending if ended before); failures and retries by cause; rewards (valid, flagged_invalid, checker_error);
curl -s "$DRUMS_URL/v1/metrics" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{episodes: .usage.episodes, create: .usage.ops.create, cost_label}'{"episodes":{"ended_pending":6,"invalid":0,"valid":17,"valid_fraction_of_judged":1},
"create":{"failed":0,"mean_s":0.686,"n":23,"p50_s":0.507,"p95_s":1.679,"samples_kept":23,"seconds_total":15.782},
"cost_label":"measured compute cost (not billed)"}Reset host metrics
Closes the current metrics window (its ledger goes to the runtime's event log) and starts a new one. Call it at the start of a measurement to measure only that run.
Python SDK: client.reset_metrics()
- Recorded in the audit log as
host.metrics_reset. - Refused for run tokens (console runs).
The closed window's summary, as drumsd returns it.
Runtime notes (drumsd)
The closed window's ledger is written to the event log.
curl -s -X POST "$DRUMS_URL/v1/metrics/reset" -H "Authorization: Bearer $DRUMS_TOKEN"Not executed for these docs: it closes the production host's metrics window that the workspace's operators read. The route, scope and audit action are taken from the gateway code.
Tasks
List tasks
Every OSWorld task in the host's pinned checkout: id, domain, instruction, and whether this host can run it.
Python SDK: client.tasks(domain=None)
- Needs
computers:readorruns:read. domainfilters by task domain:chrome,gimp,libreoffice_calc,libreoffice_impress,libreoffice_writer,multi_apps,os,thunderbird,vlc,vs_code.supported: falsemarks tasks that need the agent itself to act as root; creating a desktop with one is refused (422task_unsupported).
An array of {id, domain, instruction, supported, unsupported_reason}.
Runtime notes (drumsd)
Tasks in the host's pinned OSWorld checkout: id, domain, instruction. Query: domain.
| 400invalid_request | domain is not a domain name ([a-z0-9_]{1,64}). |
curl -s "$DRUMS_URL/v1/tasks?domain=os" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '.[0]'{"domain":"os","id":"13584542-872b-42d8-b299-866967b5c3ef",
"instruction":"I click in terminal: terminal->132x43 to change terminal size but after each reboot …",
"supported":true,"unsupported_reason":null}Get a task
One task's full OSWorld JSON (setup config, evaluator, instruction, …), by id or by a unique prefix of at least 8 characters.
Python SDK: client.task(task_id)
- Needs
computers:readorruns:read. - Pass the returned JSON as
taskwhen you create a desktop.
| idstringrequired | Task id, or a unique prefix of at least 8 characters. |
The task JSON.
Runtime notes (drumsd)
By id or unique prefix (>= 8 chars); adds _domain and _sha256 of the file.
| 404not_found | No task with that id or prefix. |
curl -s "$DRUMS_URL/v1/tasks/e0df059f" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{id, instruction, evaluator: .evaluator.func}'{"id":"e0df059f-28a6-4169-924f-b9623e7184cc",
"instruction":"I have a directory named \"todo_list_Jan_1\". Can you help me change its name into \"todo_list_Jan_2\"?",
"evaluator":"exact_match"}Images and saved states
List images and saved states
What a desktop can be created from: the base images of your launchable template versions (by name, e.g. osworld-ubuntu) and your workspace's live saved states on this host (by snapshot UUID).
Python SDK: client.images()
- Saved states of other workspaces are never listed.
captured_fromis the computer id a saved state was taken from (null for base images).
An array of checkpoint records.
Checkpoint fields (14)ShowHide
| idstring | A base image name (e.g. osworld-ubuntu) or a saved-state (snapshot) UUID. |
| captured_fromstring | null | The computer UUID the saved state was taken from (null for base images). |
| created_epochnumber | |
| expected_state_hashstring | null | |
| provenanceobject | Hashes of the runtime sources and image inspection the checkpoint was made with. |
| quiescenceobject | null | Capture readiness record: params, every attempt (wait, dump, checks, why a rejected dump was rejected). |
| staleboolean | True when the runtime cannot restore it (e.g. after a host reboot); creates from it fail. |
| stale_reasonstring | null | |
| taskobject | null | The task the captured computer was running, if any. |
| leaseobject | null | {idle_timeout_s, ttl_s} of a saved state (null for base images). |
| last_used_epochnumber | null | |
| delete_with_childrenboolean | Deleted when its last computer closes (a fork with delete_state_with_children). |
| server_ready_snumber | |
| installedboolean | null |
Runtime notes (drumsd)
Every adopted or captured checkpoint with its template server. Each checkpoint carries `provenance` (the digests of the runtime sources and of the image inspection it was made with) and `stale`/`stale_reason`: an installed image made with other ones than the running drumsd refuses new envs (409 code checkpoint_stale) until the installer re-provisions it.
curl -s "$DRUMS_URL/v1/checkpoints" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '[.[] | {id, captured_from, stale}]'[{"id":"osworld-ubuntu","captured_from":null,"stale":false}]Get an image or saved state
One record from the list above, by base image name or snapshot UUID.
| idstringrequired | Base image name or saved-state UUID. |
A checkpoint record.
Checkpoint fields (14)ShowHide
| idstring | A base image name (e.g. osworld-ubuntu) or a saved-state (snapshot) UUID. |
| captured_fromstring | null | The computer UUID the saved state was taken from (null for base images). |
| created_epochnumber | |
| expected_state_hashstring | null | |
| provenanceobject | Hashes of the runtime sources and image inspection the checkpoint was made with. |
| quiescenceobject | null | Capture readiness record: params, every attempt (wait, dump, checks, why a rejected dump was rejected). |
| staleboolean | True when the runtime cannot restore it (e.g. after a host reboot); creates from it fail. |
| stale_reasonstring | null | |
| taskobject | null | The task the captured computer was running, if any. |
| leaseobject | null | {idle_timeout_s, ttl_s} of a saved state (null for base images). |
| last_used_epochnumber | null | |
| delete_with_childrenboolean | Deleted when its last computer closes (a fork with delete_state_with_children). |
| server_ready_snumber | |
| installedboolean | null |
| 404not_found | Not a launchable image name, or not a saved state of your workspace on this host. |
curl -s "$DRUMS_URL/v1/checkpoints/$SNAPSHOT_ID" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{id, captured_from, lease, stale}'{"id":"ac6feaf8-275e-4c39-adbd-11b5fe518273","captured_from":"a5af0c19-efee-4c07-acaf-4e90a7d2dd58",
"lease":{"idle_timeout_s":1800,"ttl_s":null},"stale":false}Delete a saved state
Frees a saved state on the host (about 0.6 GB of host memory each). Base images cannot be deleted.
Python SDK: client.delete_state(state)
- Refused while a desktop made from the saved state still exists (409).
- The
installedquery parameter of drumsd is refused (403): installed images are managed by the operator. - Recorded in the audit log as
snapshot.delete.
| idstringrequired | Base image name or saved-state UUID. |
{id, deleted: true, freed_bytes}
Runtime notes (drumsd)
Stops its template server and frees its images (records kept). 409 while an env made from it exists. An installed image is refused unless installed=true (the installer uses this to re-provision a stale origin).
| 403forbidden | The id is a base image, or installed was passed. |
| 404not_found | Not a saved state of your workspace on this host (or already gone). |
| 409wrong_state | A desktop made from this saved state still exists. |
curl -s -X DELETE "$DRUMS_URL/v1/checkpoints/$SNAPSHOT_ID" -H "Authorization: Bearer $DRUMS_TOKEN"{"deleted":true,"freed_bytes":571428860,"id":"ac6feaf8-275e-4c39-adbd-11b5fe518273"}Computers
List computers
Your workspace's computers on this host. With no query: a bare array of live computers. With any query parameter: a page {items, next_after}.
Python SDK: client.envs(), client.env_records()
- Ids are computer UUIDs (the same ids the console shows).
include_destroyed=trueadds closed computers (their records are kept).limitis 1..5000 (500 by default in the paged form); passnext_afterasafterfor the next page.
| include_destroyedboolean | Default false. |
| limitinteger | Range 1..5000. |
| afterstring |
An array of computer records, or {items: [...], next_after: string | null}.
Slot fields (21)ShowHide
| checkpointstring | The base image name or saved-state UUID this computer restores to. |
| closed_epochnumber | null | |
| closed_reasonstring | null | Why it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'. |
| costCost | |
| created_epochnumber | |
| episodeinteger | |
| gatesobject | |
| idstring | The computer's UUID (the id shown in the console). |
| last_errorstring | null | |
| last_used_epochnumber | null | The last request naming this slot. |
| leaseobject | null | |
| lease_expires_epochnumber | null | When the lease runs out if the slot is not used again (null: no lease, or destroyed). |
| modestring | |
| ready_epochnumber | null | |
| reasonstring | for down and quarantined |
| restore_generationinteger | |
| state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed" | |
| taskany | |
| timingsobject | seconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total |
| updated_epochnumber | |
| validityValidity | valid only when every gate passed and the slot was never quarantined in the episode. A quarantined slot's validity is {verdict: invalid, gates: [..., not_quarantined]}. |
Runtime notes (drumsd)
With no query: every live env (not destroyed), as an array (the 0.2 SDK shape). Destroyed envs keep their records and are listed only with include_destroyed=true. Any query parameter selects the paged form {items, next_after}: envs in id order (byte order) after `after`, at most `limit` (1..5000; 500 by default when include_destroyed=true, otherwise all); next_after is the id to pass as `after` for the next page, or null on the last page.
| 400invalid_request | include_destroyed is not true/false, or limit is out of range. |
curl -s "$DRUMS_URL/v1/slots?limit=10" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{ids: [.items[].id], next_after}'{"ids":["a5af0c19-efee-4c07-acaf-4e90a7d2dd58"],"next_after":null}Create a computer
Starts a desktop from a base image or one of your saved states, optionally with an OSWorld task whose setup runs now (and after every reset). Synchronous: the reply is the computer record once it is up.
Python SDK: client.create(image, task, from_state=, setup=, idle_timeout=, ttl=)
- Every computer gets a lifetime.
ttl_sabsent or<= 0gives the workspace default (3600 s unless your owner changed it); larger values are capped at the workspace maximum (14400 s by default); the minimum is 60 s. idle_timeout_sdefaults to 1800 s and is never longer than the lifetime.- Check
statein the reply:readyis up; a reply indownorquarantinedmeans the restore or a gate failed (the SDK raisesRestoreFailed/GateFailed).validity.verdictstartspendingand resolves about 5 s after ready. - Recorded in the audit log as
computer.create; the computer appears in the console under Computers.
| checkpointstringrequired | A base image name from GET /v1/checkpoints (e.g. osworld-ubuntu) or a saved-state UUID of your workspace. |
| taskobject | null | An OSWorld task JSON (from GET /v1/tasks/{id}). Its setup runs at create and after every reset. |
| setupboolean | null | Run the task's setup after every restore (default true for a task given here). |
| ttl_snumber | null | Lifetime in seconds (clamped as described above). |
| idle_timeout_snumber | null | Closed after this many seconds with no request naming it (default 1800, at most the lifetime). |
201 with the computer record.
Slot fields (21)ShowHide
| checkpointstring | The base image name or saved-state UUID this computer restores to. |
| closed_epochnumber | null | |
| closed_reasonstring | null | Why it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'. |
| costCost | |
| created_epochnumber | |
| episodeinteger | |
| gatesobject | |
| idstring | The computer's UUID (the id shown in the console). |
| last_errorstring | null | |
| last_used_epochnumber | null | The last request naming this slot. |
| leaseobject | null | |
| lease_expires_epochnumber | null | When the lease runs out if the slot is not used again (null: no lease, or destroyed). |
| modestring | |
| ready_epochnumber | null | |
| reasonstring | for down and quarantined |
| restore_generationinteger | |
| state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed" | |
| taskany | |
| timingsobject | seconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total |
| updated_epochnumber | |
| validityValidity | valid only when every gate passed and the slot was never quarantined in the episode. A quarantined slot's validity is {verdict: invalid, gates: [..., not_quarantined]}. |
Runtime notes (drumsd)
Branch a new slot from a checkpoint (hard-linked images, template-server restore) and run every synchronous gate. The alive gate resolves `alive_after_s` after the slot answered. Body: {checkpoint (alias image, from_state), task (OSWorld JSON, optional), setup (bool, optional: run the task's setup after every restore; default true for a task given here, false for a task inherited from a captured checkpoint)}.
| 400invalid_request | Unknown body field, or a field of the wrong type. |
| 403quota_exceeded | Your workspace is at its live-computer limit (details.limit), or has no limits set. |
| 403budget_exceeded | The workspace spend cap for the period is reached. |
| 404not_found | checkpoint is not a launchable image or a ready saved state of yours. |
| 409wrong_state | The saved state is not ready yet. |
| 422task_unsupported | The task needs the agent itself to act as root (supported: false in the task list). |
| 422unsupported_runtime | The template version no longer matches its manifest, or the host's runtime is not an approved build. |
| 429overloaded | The host is full or under memory pressure. Nothing was started; retry later (Retry-After). |
curl -s "$DRUMS_URL/v1/tasks/e0df059f" -H "Authorization: Bearer $DRUMS_TOKEN" \
| jq '{checkpoint: "osworld-ubuntu", task: ., ttl_s: 900}' \
| curl -s -X POST "$DRUMS_URL/v1/slots" -H "Authorization: Bearer $DRUMS_TOKEN" -H "Content-Type: application/json" -d @- \
| jq '{id, state, episode, validity, lease}'{"id":"a5af0c19-efee-4c07-acaf-4e90a7d2dd58","state":"ready","episode":1,
"validity":{"gates":["alive_after_resume"],"verdict":"pending"},
"lease":{"idle_timeout_s":900,"ttl_s":900}}Get a computer
The computer's current record: state, episode, gate report, validity, lease and task.
Python SDK: env.refresh()
- A closed computer still answers 200 with
state: "destroyed". - Counts as a use of the computer (resets its idle clock).
| idstringrequired | Computer UUID. |
The computer record.
Slot fields (21)ShowHide
| checkpointstring | The base image name or saved-state UUID this computer restores to. |
| closed_epochnumber | null | |
| closed_reasonstring | null | Why it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'. |
| costCost | |
| created_epochnumber | |
| episodeinteger | |
| gatesobject | |
| idstring | The computer's UUID (the id shown in the console). |
| last_errorstring | null | |
| last_used_epochnumber | null | The last request naming this slot. |
| leaseobject | null | |
| lease_expires_epochnumber | null | When the lease runs out if the slot is not used again (null: no lease, or destroyed). |
| modestring | |
| ready_epochnumber | null | |
| reasonstring | for down and quarantined |
| restore_generationinteger | |
| state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed" | |
| taskany | |
| timingsobject | seconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total |
| updated_epochnumber | |
| validityValidity | valid only when every gate passed and the slot was never quarantined in the episode. A quarantined slot's validity is {verdict: invalid, gates: [..., not_quarantined]}. |
curl -s "$DRUMS_URL/v1/slots/$COMPUTER_ID" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{id, state, episode, verdict: .validity.verdict, lease_expires_epoch}'{"id":"26ce04d4-50c1-478e-a771-dbe57bb93eb1","state":"ready","episode":1,"verdict":"pending","lease_expires_epoch":1793816168.91}Close a computer
Destroys the desktop. Idempotent: closing a closed computer returns its destroyed record again.
Python SDK: env.close()
- Recorded in the audit log as
computer.delete. Measured cost stops. - Saved states made from it are not deleted.
| idstringrequired | Computer UUID. |
The destroyed record (state: "destroyed").
Slot fields (21)ShowHide
| checkpointstring | The base image name or saved-state UUID this computer restores to. |
| closed_epochnumber | null | |
| closed_reasonstring | null | Why it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'. |
| costCost | |
| created_epochnumber | |
| episodeinteger | |
| gatesobject | |
| idstring | The computer's UUID (the id shown in the console). |
| last_errorstring | null | |
| last_used_epochnumber | null | The last request naming this slot. |
| leaseobject | null | |
| lease_expires_epochnumber | null | When the lease runs out if the slot is not used again (null: no lease, or destroyed). |
| modestring | |
| ready_epochnumber | null | |
| reasonstring | for down and quarantined |
| restore_generationinteger | |
| state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed" | |
| taskany | |
| timingsobject | seconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total |
| updated_epochnumber | |
| validityValidity | valid only when every gate passed and the slot was never quarantined in the episode. A quarantined slot's validity is {verdict: invalid, gates: [..., not_quarantined]}. |
Runtime notes (drumsd)
Kill its tree, remove its bulky state, keep its records (a full archive or, for a slot that ended valid, a summary, in forgotten_dir). Idempotent: a destroyed slot returns its record with 200. Its saved state's idle clock starts; a state made by a branch with delete_state_with_children is deleted when its last slot closes.
| 409wrong_state | A console-created computer that has not started yet: deletion is requested and happens when it starts. |
| 410env_gone | The computer never got a desktop, or its desktop is already gone from the host. |
curl -s -X DELETE "$DRUMS_URL/v1/slots/$COMPUTER_ID" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{id, state, closed_epoch}'{"id":"a5af0c19-efee-4c07-acaf-4e90a7d2dd58","state":"destroyed","closed_epoch":1793813681.21}Episode
Act
Runs one action in the desktop: pyautogui code, a command (execute), a background command (launch) or a sleep. Returns the guest's result and the episode's validity at that moment.
Python SDK: env.step(action, screenshot=False, check=True)
- Actions run inside the desktop as the unprivileged user
user(uid 1000) with no network. The screen is 1920 x 1080, origin top left. - A non-zero
result.returncodeis still HTTP 200; the SDK raisesActionFailedfor it unlesscheck=False. screenshot: trueaddsscreenshot_png_b64to the reply.
| idstringrequired | Computer UUID. |
| actionobjectrequired | {"type": "pyautogui", "code"}, {"type": "execute", "command", "shell"}, {"type": "launch", "command", "shell"} or {"type": "sleep", "seconds"} (at most 60). |
| screenshotboolean | Also return a PNG screenshot after the action (base64). |
{episode, result: {status, returncode, output, error}, validity, seconds, screenshot_png_b64}
StepResult fields (5)ShowHide
| episodeinteger | |
| resultobject | guest server /execute reply |
| screenshot_png_b64string | null | |
| secondsnumber | |
| validityValidity | valid only when every gate passed and the slot was never quarantined in the episode. A quarantined slot's validity is {verdict: invalid, gates: [..., not_quarantined]}. |
Runtime notes (drumsd)
Run one action in the slot's desktop through its guest server. Returns the action result, optionally a screenshot, and the current episode's validity.
| 400invalid_request | The body is not one of the action forms. |
| 409wrong_state | The computer is quarantined or not ready (details.state). |
| 410env_gone | The computer is closed (closed_reason says why). |
| 502bad_gateway | The desktop's guest server did not answer in time. |
curl -s -X POST "$DRUMS_URL/v1/slots/$COMPUTER_ID/step" -H "Authorization: Bearer $DRUMS_TOKEN" -H "Content-Type: application/json" \
-d '{"action": {"type": "pyautogui", "code": "pyautogui.moveTo(960, 540)"}}'{"episode":1,"result":{"error":"","output":"","returncode":0,"status":"success"},
"screenshot_png_b64":null,"seconds":0.187,"validity":{"verdict":"valid"}}Screenshot
The current screen as a PNG (1920 x 1080, 8-bit RGB, about 1.7 MB).
Python SDK: env.screenshot(), env.observe()
Content-Type: image/png.
| idstringrequired | Computer UUID. |
PNG bytes.
| 410env_gone | The computer is closed. |
curl -s "$DRUMS_URL/v1/slots/$COMPUTER_ID/screenshot" -H "Authorization: Bearer $DRUMS_TOKEN" -o screen.png && file screen.pngscreen.png: PNG image data, 1920 x 1080, 8-bit/color RGB, non-interlacedGate report
The current episode's gate report and verdict (pending, valid or invalid).
Python SDK: env.gates(), env.valid(), env.wait_valid()
- The verdict is
pendingfor about 5 s after create, reset or fork (thealive_after_resumegate).
| idstringrequired | Computer UUID. |
{slot, episode, gates: {gates: [{gate, status, seconds, detail}]}, validity: {verdict, gates?}, cost}
curl -s "$DRUMS_URL/v1/slots/$COMPUTER_ID/gates" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{verdict: .validity.verdict, gates: [.gates.gates[] | "\(.gate)=\(.status)"]}'{"verdict":"valid","gates":["lock_applied=pass","lock_pre_resume=pass","init_identity=pass","cgroup=pass","mount_table=pass",
"netns=pass","guest_ready=pass","state_hash=pass","isolation=pass","alive_after_resume=pass"]}Verify
Runs the task's checker on the current desktop and returns the score together with the episode's validity. The runtime waits for the episode's last gate first.
Python SDK: env.verify(strict=True)
- Use
scoreonly whenvalidis true. On a quarantined computer the checker does not run:scoreis null andchecker_skippedsays why. - Some OSWorld checkers run a postconfig step (save a file, close a window) that changes the desktop.
| idstringrequired | Computer UUID. |
Body: none, or an empty JSON object {}.
{slot, episode, task_id, score, valid, validity, invalid_reasons, checker, checker_error, checker_skipped, gates, state, seconds}
Runtime notes (drumsd)
Waits for the episode's pending gate, runs the task's OSWorld checker (postconfig, getters, metric) and returns {score, valid, validity, invalid_reasons, checker_skipped, gates, checker_error, state, episode, task_id, seconds}. valid is true only when every gate passed and the slot is ready (never quarantined in the episode, no OOM kill); a score from any other episode is returned with valid=false. On a quarantined slot the checker is not run: score null, checker_skipped 'quarantined: <reason>'. invalid_reasons lists why in words (empty when valid).
| 400invalid_request | The computer has no task (create it with one, or load one with /task). |
| 410env_gone | The computer is closed. |
curl -s -X POST "$DRUMS_URL/v1/slots/$COMPUTER_ID/verify" -H "Authorization: Bearer $DRUMS_TOKEN" -H "Content-Type: application/json" -d '{}' \
| jq '{score, valid, invalid_reasons, checker: .checker.func, seconds}'{"score":0,"valid":true,"invalid_reasons":[],"checker":"exact_match","seconds":0.09}Reset
Starts a new episode from the image or saved state the computer was made from (and reruns the task's setup). The episode number increases; gates run again.
Python SDK: env.reset()
- No body.
- Recorded in the audit log as
computer.restart.
| idstringrequired | Computer UUID. |
The computer record of the new episode.
Slot fields (21)ShowHide
| checkpointstring | The base image name or saved-state UUID this computer restores to. |
| closed_epochnumber | null | |
| closed_reasonstring | null | Why it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'. |
| costCost | |
| created_epochnumber | |
| episodeinteger | |
| gatesobject | |
| idstring | The computer's UUID (the id shown in the console). |
| last_errorstring | null | |
| last_used_epochnumber | null | The last request naming this slot. |
| leaseobject | null | |
| lease_expires_epochnumber | null | When the lease runs out if the slot is not used again (null: no lease, or destroyed). |
| modestring | |
| ready_epochnumber | null | |
| reasonstring | for down and quarantined |
| restore_generationinteger | |
| state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed" | |
| taskany | |
| timingsobject | seconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total |
| updated_epochnumber | |
| validityValidity | valid only when every gate passed and the slot was never quarantined in the episode. A quarantined slot's validity is {verdict: invalid, gates: [..., not_quarantined]}. |
Runtime notes (drumsd)
Start a new episode: kill the tree (cgroup.kill), swap in a fresh upper layer (warm spare), restore from the slot's checkpoint, run every gate. Allowed from `ready` and `down`. A failure before any task ran is retried per the recovery policy; every attempt and its cost is in `cost.attempts`.
| 409wrong_state | The computer is quarantined (release it first). |
| 410env_gone | The computer is closed. |
curl -s -X POST "$DRUMS_URL/v1/slots/$COMPUTER_ID/reset" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{state, episode, verdict: .validity.verdict}'{"state":"ready","episode":2,"verdict":"pending"}Load a task
Loads an OSWorld task into a ready computer: its setup runs now and after every reset.
Python SDK: env.load_task(task)
- Recorded in the audit log as
computer.task.
| idstringrequired | Computer UUID. |
| taskobjectrequired | An OSWorld task JSON. |
The computer record.
Slot fields (21)ShowHide
| checkpointstring | The base image name or saved-state UUID this computer restores to. |
| closed_epochnumber | null | |
| closed_reasonstring | null | Why it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'. |
| costCost | |
| created_epochnumber | |
| episodeinteger | |
| gatesobject | |
| idstring | The computer's UUID (the id shown in the console). |
| last_errorstring | null | |
| last_used_epochnumber | null | The last request naming this slot. |
| leaseobject | null | |
| lease_expires_epochnumber | null | When the lease runs out if the slot is not used again (null: no lease, or destroyed). |
| modestring | |
| ready_epochnumber | null | |
| reasonstring | for down and quarantined |
| restore_generationinteger | |
| state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed" | |
| taskany | |
| timingsobject | seconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total |
| updated_epochnumber | |
| validityValidity | valid only when every gate passed and the slot was never quarantined in the episode. A quarantined slot's validity is {verdict: invalid, gates: [..., not_quarantined]}. |
Runtime notes (drumsd)
Run an OSWorld task's setup in this ready slot now and after every reset. On setup failure the slot is quarantined (422).
| 410env_gone | The computer is closed. |
| 422task_unsupported | The task needs the agent itself to act as root. |
| 422unsupported_runtime | The task's setup failed; the computer is quarantined. |
curl -s "$DRUMS_URL/v1/tasks/e0df059f" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{task: .}' \
| curl -s -X POST "$DRUMS_URL/v1/slots/$COMPUTER_ID/task" -H "Authorization: Bearer $DRUMS_TOKEN" -H "Content-Type: application/json" -d @- \
| jq '{state, task: .task.id}'{"state":"ready","task":"e0df059f-28a6-4169-924f-b9623e7184cc"}Computer metrics
This computer's operation latencies, episodes by verdict, failures and retries, lifetime, and its share of the host's measured compute cost by desktop-seconds.
Python SDK: env.metrics()
- Cost fields are measured compute cost, not a bill.
| idstringrequired | Computer UUID. |
{slot, state, episode, lifetime_s, usage, last_op_timings, last_cost, cost}
Runtime notes (drumsd)
This slot's ledger (as /v1/metrics), lifetime, last op timings and attempts, and its share of the host cost by slot-seconds.
curl -s "$DRUMS_URL/v1/slots/$COMPUTER_ID/metrics" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{state, lifetime_s, episodes: .usage.episodes}'{"state":"ready","lifetime_s":12.103,"episodes":{"ended_pending":0,"invalid":0,"valid":1,"valid_fraction_of_judged":1}}Saved states and forks
Save state
Saves the computer's current state (memory and disk) as a saved state on the host; the computer keeps running. Create computers from it with checkpoint: <id>.
Python SDK: env.save(name=None, quiesce_timeout=None, idle_timeout=, ttl=)
- The runtime first waits until the process tree is quiet (no process or thread starting or exiting for 1 s), so it is safe right after an action.
- The reply's
idis a snapshot UUID; the console lists it under Snapshots.idin the body is kept as the snapshot's display name only. - Saved states live in host memory: they are lost if the host reboots. The host holds a limited number (
quota_exceededwithdetails.limit: max_saved_states). - Recorded in the audit log as
computer.snapshot.
| idstringrequired | Computer UUID. |
| idstring | Display name (at most 128 characters). |
| quiesce_timeout_snumber | How long to wait for a quiet process tree (0..300, default 30). |
| quiesce_window_snumber | How long the tree must stay quiet (0..60, default 1). |
| idle_timeout_snumber | Saved-state lease: deleted after this many seconds with no computer made from it (default 1800). |
| ttl_snumber | Saved-state lifetime once no computer uses it. |
The saved state's checkpoint record.
Checkpoint fields (14)ShowHide
| idstring | A base image name (e.g. osworld-ubuntu) or a saved-state (snapshot) UUID. |
| captured_fromstring | null | The computer UUID the saved state was taken from (null for base images). |
| created_epochnumber | |
| expected_state_hashstring | null | |
| provenanceobject | Hashes of the runtime sources and image inspection the checkpoint was made with. |
| quiescenceobject | null | Capture readiness record: params, every attempt (wait, dump, checks, why a rejected dump was rejected). |
| staleboolean | True when the runtime cannot restore it (e.g. after a host reboot); creates from it fail. |
| stale_reasonstring | null | |
| taskobject | null | The task the captured computer was running, if any. |
| leaseobject | null | {idle_timeout_s, ttl_s} of a saved state (null for base images). |
| last_used_epochnumber | null | |
| delete_with_childrenboolean | Deleted when its last computer closes (a fork with delete_state_with_children). |
| server_ready_snumber | |
| installedboolean | null |
Runtime notes (drumsd)
Checkpoint the running slot (`capture --leave-running`: the tree is frozen for the dump, the upper layer is copied at CRIU's post-dump action, the tree continues) and adopt it as a new checkpoint with its own template server. Capture readiness: the dump happens only after the slot's tree shows no task exiting, no unreaped zombie and no process or thread starting or exiting for the window; a dump whose CRIU-seized tree differs from that sample, or whose processes exit in the source right after, is discarded (records kept under forgotten/) and retried. 409 with `code: not_quiescent` (and `unstable`, `quiescence`) when no good dump is possible in time: nothing is captured and the env stays ready. The checkpoint's `quiescence` field records every attempt.
| 403quota_exceeded | The host's saved-state limit is reached. |
| 409wrong_state | The computer is not ready. |
| 409not_quiescent | The process tree did not become quiet within the timeout. Nothing was captured; the computer is still ready. |
curl -s -X POST "$DRUMS_URL/v1/slots/$COMPUTER_ID/capture" -H "Authorization: Bearer $DRUMS_TOKEN" -H "Content-Type: application/json" -d '{"id": "after-setup"}' \
| jq '{id, captured_from, expected_state_hash, seconds: .quiescence.seconds}'{"id":"ac6feaf8-275e-4c39-adbd-11b5fe518273","captured_from":"a5af0c19-efee-4c07-acaf-4e90a7d2dd58",
"expected_state_hash":"44f4a841…","seconds":2.185}Fork
Saves the computer's state and starts n independent computers from it, concurrently; the parent keeps running. Each child starts a new episode with its own gate report.
Python SDK: env.fork(n, strict=True, quiesce_timeout=None, keep_state=True, idle_timeout=, ttl=, state_idle_timeout=, state_ttl=)
nis 1..64 in the body and at most the host'smax_fork_n; the children count against your live-computer limit.- Children get lifetimes like a create (
ttl_sclamped to the workspace default and maximum). delete_state_with_children: truedeletes the fork's saved state when its last child closes (the SDK'skeep_state=False). Otherwise the saved state stays until you delete it or its lease runs out.- A child that did not start is returned as
{error}inchildren. - Recorded in the audit log as
computer.fork.
| idstringrequired | Computer UUID. |
| nintegerrequired | Number of copies, 1..64 (and at most the host's limit). |
| delete_state_with_childrenboolean | Delete the saved state when the last child closes. |
| ttl_snumber | Children's lifetime (clamped). |
| idle_timeout_snumber | Children's idle timeout. |
| quiesce_timeout_snumber | 0..300. |
| quiesce_window_snumber | 0..60. |
| state_idle_timeout_snumber | The saved state's idle lease. |
| state_ttl_snumber | The saved state's lifetime once unused. |
{checkpoint, state, children: [computer record | {error}], capture_s, children_ready_s, seconds, delete_state_with_children}
BranchResult fields (7)ShowHide
| capture_snumber | |
| checkpointCheckpoint | |
| childrenSlot[] | |
| children_ready_snumber | |
| delete_state_with_childrenboolean | |
| secondsnumber | |
| statestring | The fork's saved-state UUID (the same as checkpoint.id). |
Runtime notes (drumsd)
Capture the slot's current state, then create `n` children from it concurrently. The parent keeps running its episode. Capture readiness: the dump happens only after the slot's tree shows no task exiting, no unreaped zombie and no process or thread starting or exiting for the window; a dump whose CRIU-seized tree differs from that sample, or whose processes exit in the source right after, is discarded (records kept under forgotten/) and retried. 409 with `code: not_quiescent` (and `unstable`, `quiescence`) when no good dump is possible in time: nothing is captured and the env stays ready. The checkpoint's `quiescence` field records every attempt.
| 400invalid_request | n is missing or not 1..64. |
| 403quota_exceeded | n is above the host's max_fork_n, or the copies would exceed your live-computer limit, or the saved-state limit is reached. |
| 409not_quiescent | The process tree did not become quiet within the timeout. |
| 429overloaded | The host is full. |
curl -s -X POST "$DRUMS_URL/v1/slots/$COMPUTER_ID/branch" -H "Authorization: Bearer $DRUMS_TOKEN" -H "Content-Type: application/json" \
-d '{"n": 2, "delete_state_with_children": true, "ttl_s": 600}' \
| jq '{state, seconds, children: [.children[] | {id, state, verdict: .validity.verdict}]}'{"state":"3b71ae6d-9228-44e8-a870-bdfb1c688501","seconds":2.674,
"children":[{"id":"626baf25-afa2-4519-8d63-15eb1abfc8ac","state":"ready","verdict":"pending"},
{"id":"104cf8d3-d161-4101-89f6-b04149978fd8","state":"ready","verdict":"pending"}]}Wait until quiet
Blocks until the computer's process tree is safe to capture, without capturing. Save and fork already do this; use it to time the wait.
Python SDK: env.wait_quiescent(timeout=None, window=None)
| idstringrequired | Computer UUID. |
| timeout_snumber | 0..600 (default 30). |
| window_snumber | 0..60 (default 1). |
{quiescent, waited_s, window_s, timeout_s, processes, threads, unstable, …}
Runtime notes (drumsd)
Block until the slot's process tree is safe to capture (no task exiting, no unreaped zombie, no process or thread starting or exiting for `window_s`). Holds the slot's operation lock while it waits. capture/branch do this themselves; this route waits without capturing. 409 `not_quiescent` on a timeout.
| 409not_quiescent | Not quiet within the timeout (unstable lists what kept changing). |
curl -s -X POST "$DRUMS_URL/v1/slots/$COMPUTER_ID/quiescent" -H "Authorization: Bearer $DRUMS_TOKEN" -H "Content-Type: application/json" -d '{}' | jq '{quiescent, waited_s, processes}'{"quiescent":true,"waited_s":1.013,"processes":53}Quarantine
Quarantine
Marks the computer's state as untrusted: the episode becomes invalid and steps, resets and saves are refused. Use it when your harness detects a broken episode.
Python SDK: env.quarantine(reason="client")
- Recorded in the audit log as
computer.quarantine.
| idstringrequired | Computer UUID. |
| reasonstring | At most 200 characters; shown in invalid_reasons as quarantined: <reason>. |
The record, state: "quarantined".
Slot fields (21)ShowHide
| checkpointstring | The base image name or saved-state UUID this computer restores to. |
| closed_epochnumber | null | |
| closed_reasonstring | null | Why it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'. |
| costCost | |
| created_epochnumber | |
| episodeinteger | |
| gatesobject | |
| idstring | The computer's UUID (the id shown in the console). |
| last_errorstring | null | |
| last_used_epochnumber | null | The last request naming this slot. |
| leaseobject | null | |
| lease_expires_epochnumber | null | When the lease runs out if the slot is not used again (null: no lease, or destroyed). |
| modestring | |
| ready_epochnumber | null | |
| reasonstring | for down and quarantined |
| restore_generationinteger | |
| state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed" | |
| taskany | |
| timingsobject | seconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total |
| updated_epochnumber | |
| validityValidity | valid only when every gate passed and the slot was never quarantined in the episode. A quarantined slot's validity is {verdict: invalid, gates: [..., not_quarantined]}. |
Runtime notes (drumsd)
Stop using the slot until it is released or destroyed.
| 410env_gone | The computer is closed. |
curl -s -X POST "$DRUMS_URL/v1/slots/$COMPUTER_ID/quarantine" -H "Authorization: Bearer $DRUMS_TOKEN" -H "Content-Type: application/json" -d '{"reason": "harness check failed"}' \
| jq '{state, reason, validity}'{"state":"quarantined","reason":"harness check failed","validity":{"gates":["not_quarantined"],"verdict":"invalid"}}Release
Kills a quarantined computer's processes; it becomes down, and a reset restores it with a new, judged episode.
Python SDK: env.release()
- Recorded in the audit log as
computer.release.
| idstringrequired | Computer UUID. |
Body: none, or an empty JSON object {}.
The record, state: "down".
Slot fields (21)ShowHide
| checkpointstring | The base image name or saved-state UUID this computer restores to. |
| closed_epochnumber | null | |
| closed_reasonstring | null | Why it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'. |
| costCost | |
| created_epochnumber | |
| episodeinteger | |
| gatesobject | |
| idstring | The computer's UUID (the id shown in the console). |
| last_errorstring | null | |
| last_used_epochnumber | null | The last request naming this slot. |
| leaseobject | null | |
| lease_expires_epochnumber | null | When the lease runs out if the slot is not used again (null: no lease, or destroyed). |
| modestring | |
| ready_epochnumber | null | |
| reasonstring | for down and quarantined |
| restore_generationinteger | |
| state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed" | |
| taskany | |
| timingsobject | seconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total |
| updated_epochnumber | |
| validityValidity | valid only when every gate passed and the slot was never quarantined in the episode. A quarantined slot's validity is {verdict: invalid, gates: [..., not_quarantined]}. |
Runtime notes (drumsd)
Kills the quarantined tree; the slot goes to `down` and the next reset restores it.
| 409wrong_state | The computer is not quarantined. |
| 410env_gone | The computer is closed. |
curl -s -X POST "$DRUMS_URL/v1/slots/$COMPUTER_ID/release" -H "Authorization: Bearer $DRUMS_TOKEN" -H "Content-Type: application/json" -d '{}' | jq '{state, reason}'{"state":"down","reason":"released from quarantine: harness check failed"}Runs, keys and audit
List runs
The workspace's runs (evaluations and scripted tests), newest first. Scope runs:read.
- Query:
kind(evaluationorscripted_test),status(a run status),limit(1 to 200, default 50),cursor(thenext_cursorof the previous page). - Each item is the run record without the runner lease (
leased_by,lease_until). - This route exists only on the gateway; a plain drumsd answers 404.
| kind"evaluation" | "scripted_test" | evaluation or scripted_test |
| statusstring | a run status |
| limitinteger | 1..200 (default 50) Range 1..200. |
| cursorstring | next_cursor of the previous page |
{items: [Run...], next_cursor}
Runtime notes (drumsd)
Runs of the workspace (evaluations and scripted tests), newest first. Scope runs:read.
| 400invalid_request | kind is not a run kind, status not a run status, or limit is out of range. |
curl -s "$DRUMS_URL/v1/runs?kind=evaluation&limit=2" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{n: (.items | length), next_cursor}'Not executed for these docs: the workspace behind the docs key has no runs; the route, scope and reply shape are taken from the gateway code and its tests.
Submit a run
Queues a run from a RunSpec, the same document the console's New run form submits. The host's runner picks it up. Scope runs:write.
- The body is the RunSpec:
name,kind(evaluationorscripted_test),task_set_id,task_ids,template_version_id,agent({kind: scripted}or{kind: model, endpoint_url, model, secret_ref_id}),execution(parallelism,fork_n,prefix_steps,max_steps,rounds),budget(usd,max_duration_s). - Checked before anything is queued: the task set and template version are visible to the workspace, every task is in the set and supported, a scripted agent names tasks with a recipe, a model agent's endpoint host is allowed by its secret reference, the execution fits the workspace limits, and the budget fits the remaining spend of the period. The first problem is the error message; every problem is in
details.issues. Idempotency-Keyheader: the same key with the same body returns the same run (created: false, 200). Without the header every call is a new run.- Recorded in the audit log as
run.create.
| namestringrequired | Lowercase letters, digits and dashes, up to 64. |
| kindstringrequired | evaluation (model agent) or scripted_test (scripted agent). |
| task_set_idstringrequired | A task set of the workspace or a platform set. |
| task_idsstring[]required | Unique ids from that set, 1 to 366. |
| template_version_idstringrequired | A published template version whose image is on the workspace's host. |
| agentobjectrequired | {kind: "scripted"} or {kind: "model", endpoint_url, model, secret_ref_id, max_tokens?, temperature?}. |
| executionobjectrequired | parallelism 1..8, fork_n 1..8 (default 1), prefix_steps 0..20, max_steps 1..50, rounds 1..50. |
| budgetobjectrequired | usd (above 0), max_duration_s (60..86400), verified_target (optional). |
| retry_of_run_idstring | An ended run this one retries. |
201 {run_id, operation_id, status: "queued", created: true}; 200 with created: false when the Idempotency-Key already made a run.
Runtime notes (drumsd)
Queue a run from a RunSpec (name, kind, task_set_id, task_ids, template_version_id, agent, execution, budget). The spec is checked against the workspace (task set, template version, secret reference, limits, remaining spend) before it is queued; the host's runner picks it up. `Idempotency-Key` header: the same key with the same body returns the same run. Scope runs:write.
| 400invalid_request | The spec does not parse, or a fork has no prefix step. |
| 403quota_exceeded | Parallelism or copies per fork above the workspace limit, too many computers at once, or too many runs already active. |
| 403budget_exceeded | The budget is above the spend left in this period. |
| 404not_found | The task set, a task, the template version or the secret reference is not in this workspace. |
| 409wrong_state | The Idempotency-Key was used with a different body, retry_of_run_id names a run that has not ended, or the template version is not published. |
| 422unsupported_runtime | A task is not supported, the template version's image is not the host's, or the run kind is not offered in v1. |
| 429overloaded | The compute pool is draining; new runs cannot start right now (Retry-After). |
jq -n --arg task_set "$TASK_SET_ID" --arg template_version "$TEMPLATE_VERSION_ID" '{
name: "smoke-1", kind: "scripted_test",
task_set_id: $task_set, task_ids: ["e0df059f-28a6-4169-924f-b9623e7184cc"],
template_version_id: $template_version, agent: {kind: "scripted"},
execution: {parallelism: 1, fork_n: 1, prefix_steps: 5, max_steps: 3, rounds: 1},
budget: {usd: 1, max_duration_s: 600}}' \
| curl -s -X POST "$DRUMS_URL/v1/runs" -H "Authorization: Bearer $DRUMS_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" -d @-Not executed for these docs: it would queue a run on the production host and spend compute. The body, checks and reply are taken from the gateway code and its tests.
Get a run
One run with an attempts summary and its last 20 events. Scope runs:read.
- The run record without the runner lease, plus
attempts: {total, done, succeeded, verified_valid}andevents(newest first:seq,type,event_at,attempt_id,payload). - A run of another workspace is 404, never 403.
| idstringrequired | Base image name or saved-state UUID. |
The run, attempts and events.
Runtime notes (drumsd)
The run (no lease internals), an attempts summary {total, done, succeeded, verified_valid} and its last 20 events. Scope runs:read.
| 404not_found | Not a run of your workspace. |
curl -s "$DRUMS_URL/v1/runs/$RUN_ID" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{status, stop_reason, attempts, events: (.events | length)}'Not executed for these docs: it needs a run id from the submit route above, which is not run either.
List API keys
Metadata of the workspace's keys: id, name, prefix, scopes, owner, expiry, last use, revocation. Never a secret. Any key.
- The prefix is the 12 characters after
drums_sk_; the key itself is stored only as a hash and cannot be shown again.
{items: [{id, name, prefix, scopes, owner_user_id, expires_at, last_used_at, revoked_at, rotated_from, created_at}...]}
Runtime notes (drumsd)
Metadata of the workspace's keys (id, name, prefix, scopes, owner, expiry, last use, revocation). Never a secret. Any key.
curl -s "$DRUMS_URL/v1/api-keys" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '.items[] | {name, prefix, scopes, revoked_at}'Not executed for these docs: it would list the names and prefixes of the production workspace's keys. The reply shape is taken from the gateway code and its tests.
Create an API key
Mints a key owned by the calling key's owner. The plaintext key is in this reply once and nowhere else.
keyin the reply is the whole key (prefix and 43-character secret), shown once; the example shortens it.- Only an owner or admin key may create keys (403 otherwise).
- The new key's scopes cannot exceed the calling key's effective scopes (its scopes intersected with its owner's role); the refused scopes are listed in
details.scopes. - Recorded in the audit log as
api_key.create(name, prefix and scopes; never the key).
| namestringrequired | 1 to 80 characters. |
| scopesstring[]required | Unique, at least one of computers:read, computers:write, runs:read, runs:write, templates:read, templates:write, usage:read, audit:read. |
| expires_in_daysinteger | 1 to 365; absent means no expiry. |
201 {id, prefix, key, expires_at}.
Runtime notes (drumsd)
Mint a key owned by the calling key's owner. Only an owner or admin key may call this; the new key's scopes cannot exceed the calling key's effective scopes. The plaintext key is in this reply once and nowhere else.
| 400invalid_request | Empty name, no scopes, an unknown scope, or an expiry out of range. |
| 403forbidden | The calling key is a member's key, or asks for a scope it does not have itself. |
curl -s -X POST "$DRUMS_URL/v1/api-keys" -H "Authorization: Bearer $DRUMS_TOKEN" -H "Content-Type: application/json" -d '{"name": "ci", "scopes": ["computers:read", "runs:read"], "expires_in_days": 30}'Not executed for these docs: it would mint a live credential in the production workspace.
Revoke an API key
Revokes a key now; it is refused on its next call. Idempotent.
- Only an owner or admin key may revoke keys (403 otherwise).
- A key cannot revoke itself (400): use another owner or admin key, or the console.
- A second call for an already revoked key returns its original
revoked_atwithalready: true. - Recorded in the audit log as
api_key.revoke.
| idstringrequired | Base image name or saved-state UUID. |
{id, revoked_at, already}
Runtime notes (drumsd)
Revoke now; the key is refused on its next call. Only an owner or admin key; a key cannot revoke itself (400).
| 400invalid_request | The id is the calling key's own id. |
| 403forbidden | The calling key is a member's key. |
| 404not_found | Not a key of your workspace. |
curl -s -X DELETE "$DRUMS_URL/v1/api-keys/$KEY_ID" -H "Authorization: Bearer $DRUMS_TOKEN"Not executed for these docs: it would revoke a key of the production workspace.
Read the audit log
The workspace's audit events, newest first: who did what, when, with what outcome. Scope audit:read (owner and admin keys only; the role filter drops it from a member's key).
- Query:
action(an exact action such ascomputer.create, or a prefix such asmcp, which matches everymcp.<tool>row),limit(1 to 200, default 50),cursor(thenext_cursorof the previous page; it is the id of the last row). - Rows:
id,actor_kind(user,api_key,operator,system,mcp),actor_user_id,actor_api_key_id,action,target_type,target_id,outcome,error_code,request_id,origin,source_ip_redacted,metadata,created_at. Metadata never holds secrets, screenshots or file contents.
| actionstring | action or action prefix, e.g. mcp or computer.create |
| limitinteger | 1..200 (default 50) Range 1..200. |
| cursorstring | next_cursor of the previous page |
{items: [AuditEvent...], next_cursor}
Runtime notes (drumsd)
Audit events of the workspace, newest first. `action` filters by exact action or prefix (`mcp` matches every `mcp.<tool>` row). Scope audit:read.
| 400invalid_request | action is not a dotted lower-case name, or limit is out of range. |
curl -s "$DRUMS_URL/v1/audit?action=computer&limit=2" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '.items[] | {action, actor_kind, outcome, created_at}'Not executed for these docs: the docs key has no `audit:read` scope by design. The query, scope and reply shape are taken from the gateway code and its tests.