Skip to content

Reference

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 except GET /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 an X-Request-Id header. See Errors.
  • Names: a computer is a slot here, a saved state a checkpoint, save is capture and fork is branch.
  • Examples assume DRUMS_URL and DRUMS_TOKEN exported as in the quickstart and jq installed. COMPUTER_ID, SNAPSHOT_ID, RUN_ID and KEY_ID are ids from earlier calls; TASK_SET_ID and TEMPLATE_VERSION_ID are ids the console shows under Runs and Templates.

Endpoints

EndpointScopeSDK
GET/v1/healthany valid keyclient.health
GET/v1/metricsusage:readclient.metrics
POST/v1/metrics/resetcomputers:writeclient.reset_metrics
GET/v1/taskscomputers:read or runs:readclient.tasks
GET/v1/tasks/{id}computers:read or runs:readclient.task
GET/v1/checkpointscomputers:readclient.images
GET/v1/checkpoints/{id}computers:read—
DELETE/v1/checkpoints/{id}computers:writeclient.delete_state
GET/v1/slotscomputers:readclient.envs
POST/v1/slotscomputers:writeclient.create
GET/v1/slots/{id}computers:readenv.refresh
DELETE/v1/slots/{id}computers:writeenv.close
POST/v1/slots/{id}/stepcomputers:writeenv.step
GET/v1/slots/{id}/screenshotcomputers:readenv.screenshot
GET/v1/slots/{id}/gatescomputers:readenv.gates
POST/v1/slots/{id}/verifycomputers:writeenv.verify
POST/v1/slots/{id}/resetcomputers:writeenv.reset
POST/v1/slots/{id}/taskcomputers:writeenv.load_task
GET/v1/slots/{id}/metricscomputers:readenv.metrics
POST/v1/slots/{id}/capturecomputers:writeenv.save
POST/v1/slots/{id}/branchcomputers:writeenv.fork
POST/v1/slots/{id}/quiescentcomputers:writeenv.wait_quiescent
POST/v1/slots/{id}/quarantinecomputers:writeenv.quarantine
POST/v1/slots/{id}/releasecomputers:writeenv.release
GET/v1/runsruns:read—
POST/v1/runsruns:write—
GET/v1/runs/{id}runs:read—
GET/v1/api-keysany valid key—
POST/v1/api-keysany valid key—
DELETE/v1/api-keys/{id}any valid key—
GET/v1/auditaudit:read—

Unauthenticated routes

RouteReply
GET /gw/healthz{"ok":true}: the gateway process is up
GET /gw/openapi.jsonThe OpenAPI 3.1 document of the routes below

Common errors

Any authenticated route can answer with these; each endpoint below lists only its own.

Errors (besides the common ones)

401unauthenticatedThe 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.
403forbiddenThe key's effective scopes (key scopes intersected with the owner's current role) do not include the route's scope.
404not_foundNo 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_limitedMore 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_failureThe 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

GET/gw/v1/healthscope any valid key

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.
  • checkpoints lists only the base images of template versions your workspace may launch on this host; saved states are not listed here.
  • capacity.live counts live desktops on the host; capacity.max is the host's desktop limit. The host serves one workspace.

Response

{ok, version, criu: {sha256}, image_sha256, checkpoints: [{id, stale_reason}], limits, leases: {defaults}, capacity: {live, max}}

Request
curl -s "$DRUMS_URL/v1/health" -H "Authorization: Bearer $DRUMS_TOKEN"
Response (from the verification run, trimmed)
{"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

GET/gw/v1/metricsscope usage:read

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.

Response

{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);

Request
curl -s "$DRUMS_URL/v1/metrics" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{episodes: .usage.episodes, create: .usage.ops.create, cost_label}'
Response (from the verification run, trimmed)
{"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

POST/gw/v1/metrics/resetscope computers:write

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

Response

The closed window's summary, as drumsd returns it.

Runtime notes (drumsd)

The closed window's ledger is written to the event log.

Request
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

GET/gw/v1/tasksscope computers:read or runs:read

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:read or runs:read.
  • domain filters by task domain: chrome, gimp, libreoffice_calc, libreoffice_impress, libreoffice_writer, multi_apps, os, thunderbird, vlc, vs_code.
  • supported: false marks tasks that need the agent itself to act as root; creating a desktop with one is refused (422 task_unsupported).

Response

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.

Errors (besides the common ones)

400invalid_requestdomain is not a domain name ([a-z0-9_]{1,64}).
Request
curl -s "$DRUMS_URL/v1/tasks?domain=os" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '.[0]'
Response (from the verification run, trimmed)
{"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

GET/gw/v1/tasks/{id}scope computers:read or runs:read

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:read or runs:read.
  • Pass the returned JSON as task when you create a desktop.

Path parameters

idstringrequiredTask id, or a unique prefix of at least 8 characters.

Response

The task JSON.

Runtime notes (drumsd)

By id or unique prefix (>= 8 chars); adds _domain and _sha256 of the file.

Errors (besides the common ones)

404not_foundNo task with that id or prefix.
Request
curl -s "$DRUMS_URL/v1/tasks/e0df059f" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{id, instruction, evaluator: .evaluator.func}'
Response (from the verification run, trimmed)
{"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

GET/gw/v1/checkpointsscope computers:read

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_from is the computer id a saved state was taken from (null for base images).

Response

An array of checkpoint records.

Checkpoint fields (14)Show
idstringA base image name (e.g. osworld-ubuntu) or a saved-state (snapshot) UUID.
captured_fromstring | nullThe computer UUID the saved state was taken from (null for base images).
created_epochnumber
expected_state_hashstring | null
provenanceobjectHashes of the runtime sources and image inspection the checkpoint was made with.
quiescenceobject | nullCapture readiness record: params, every attempt (wait, dump, checks, why a rejected dump was rejected).
stalebooleanTrue when the runtime cannot restore it (e.g. after a host reboot); creates from it fail.
stale_reasonstring | null
taskobject | nullThe 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_childrenbooleanDeleted 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.

Request
curl -s "$DRUMS_URL/v1/checkpoints" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '[.[] | {id, captured_from, stale}]'
Response (from the verification run, trimmed)
[{"id":"osworld-ubuntu","captured_from":null,"stale":false}]

Get an image or saved state

GET/gw/v1/checkpoints/{id}scope computers:read

One record from the list above, by base image name or snapshot UUID.

Path parameters

idstringrequiredBase image name or saved-state UUID.

Response

A checkpoint record.

Checkpoint fields (14)Show
idstringA base image name (e.g. osworld-ubuntu) or a saved-state (snapshot) UUID.
captured_fromstring | nullThe computer UUID the saved state was taken from (null for base images).
created_epochnumber
expected_state_hashstring | null
provenanceobjectHashes of the runtime sources and image inspection the checkpoint was made with.
quiescenceobject | nullCapture readiness record: params, every attempt (wait, dump, checks, why a rejected dump was rejected).
stalebooleanTrue when the runtime cannot restore it (e.g. after a host reboot); creates from it fail.
stale_reasonstring | null
taskobject | nullThe 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_childrenbooleanDeleted when its last computer closes (a fork with delete_state_with_children).
server_ready_snumber
installedboolean | null

Errors (besides the common ones)

404not_foundNot a launchable image name, or not a saved state of your workspace on this host.
Request
curl -s "$DRUMS_URL/v1/checkpoints/$SNAPSHOT_ID" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{id, captured_from, lease, stale}'
Response (from the verification run, trimmed)
{"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

DELETE/gw/v1/checkpoints/{id}scope computers:write

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 installed query parameter of drumsd is refused (403): installed images are managed by the operator.
  • Recorded in the audit log as snapshot.delete.

Path parameters

idstringrequiredBase image name or saved-state UUID.

Response

{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).

Errors (besides the common ones)

403forbiddenThe id is a base image, or installed was passed.
404not_foundNot a saved state of your workspace on this host (or already gone).
409wrong_stateA desktop made from this saved state still exists.
Request
curl -s -X DELETE "$DRUMS_URL/v1/checkpoints/$SNAPSHOT_ID" -H "Authorization: Bearer $DRUMS_TOKEN"
Response (from the verification run, trimmed)
{"deleted":true,"freed_bytes":571428860,"id":"ac6feaf8-275e-4c39-adbd-11b5fe518273"}

Computers

List computers

GET/gw/v1/slotsscope computers:read

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=true adds closed computers (their records are kept). limit is 1..5000 (500 by default in the paged form); pass next_after as after for the next page.

Query parameters

include_destroyedbooleanDefault false.
limitintegerRange 1..5000.
afterstring

Response

An array of computer records, or {items: [...], next_after: string | null}.

Slot fields (21)Show
checkpointstringThe base image name or saved-state UUID this computer restores to.
closed_epochnumber | null
closed_reasonstring | nullWhy it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'.
costCost
created_epochnumber
episodeinteger
gatesobject
idstringThe computer's UUID (the id shown in the console).
last_errorstring | null
last_used_epochnumber | nullThe last request naming this slot.
leaseobject | null
lease_expires_epochnumber | nullWhen the lease runs out if the slot is not used again (null: no lease, or destroyed).
modestring
ready_epochnumber | null
reasonstringfor down and quarantined
restore_generationinteger
state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed"
taskany
timingsobjectseconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total
updated_epochnumber
validityValidityvalid 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.

Errors (besides the common ones)

400invalid_requestinclude_destroyed is not true/false, or limit is out of range.
Request
curl -s "$DRUMS_URL/v1/slots?limit=10" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{ids: [.items[].id], next_after}'
Response (from the verification run, trimmed)
{"ids":["a5af0c19-efee-4c07-acaf-4e90a7d2dd58"],"next_after":null}

Create a computer

POST/gw/v1/slotsscope computers:write

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_s absent or <= 0 gives 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_s defaults to 1800 s and is never longer than the lifetime.
  • Check state in the reply: ready is up; a reply in down or quarantined means the restore or a gate failed (the SDK raises RestoreFailed/GateFailed). validity.verdict starts pending and resolves about 5 s after ready.
  • Recorded in the audit log as computer.create; the computer appears in the console under Computers.

Body (JSON)

checkpointstringrequiredA base image name from GET /v1/checkpoints (e.g. osworld-ubuntu) or a saved-state UUID of your workspace.
taskobject | nullAn OSWorld task JSON (from GET /v1/tasks/{id}). Its setup runs at create and after every reset.
setupboolean | nullRun the task's setup after every restore (default true for a task given here).
ttl_snumber | nullLifetime in seconds (clamped as described above).
idle_timeout_snumber | nullClosed after this many seconds with no request naming it (default 1800, at most the lifetime).

Response

201 with the computer record.

Slot fields (21)Show
checkpointstringThe base image name or saved-state UUID this computer restores to.
closed_epochnumber | null
closed_reasonstring | nullWhy it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'.
costCost
created_epochnumber
episodeinteger
gatesobject
idstringThe computer's UUID (the id shown in the console).
last_errorstring | null
last_used_epochnumber | nullThe last request naming this slot.
leaseobject | null
lease_expires_epochnumber | nullWhen the lease runs out if the slot is not used again (null: no lease, or destroyed).
modestring
ready_epochnumber | null
reasonstringfor down and quarantined
restore_generationinteger
state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed"
taskany
timingsobjectseconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total
updated_epochnumber
validityValidityvalid 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)}.

Errors (besides the common ones)

400invalid_requestUnknown body field, or a field of the wrong type.
403quota_exceededYour workspace is at its live-computer limit (details.limit), or has no limits set.
403budget_exceededThe workspace spend cap for the period is reached.
404not_foundcheckpoint is not a launchable image or a ready saved state of yours.
409wrong_stateThe saved state is not ready yet.
422task_unsupportedThe task needs the agent itself to act as root (supported: false in the task list).
422unsupported_runtimeThe template version no longer matches its manifest, or the host's runtime is not an approved build.
429overloadedThe host is full or under memory pressure. Nothing was started; retry later (Retry-After).
Request
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}'
Response (from the verification run, trimmed)
{"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

GET/gw/v1/slots/{id}scope computers:read

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

Path parameters

idstringrequiredComputer UUID.

Response

The computer record.

Slot fields (21)Show
checkpointstringThe base image name or saved-state UUID this computer restores to.
closed_epochnumber | null
closed_reasonstring | nullWhy it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'.
costCost
created_epochnumber
episodeinteger
gatesobject
idstringThe computer's UUID (the id shown in the console).
last_errorstring | null
last_used_epochnumber | nullThe last request naming this slot.
leaseobject | null
lease_expires_epochnumber | nullWhen the lease runs out if the slot is not used again (null: no lease, or destroyed).
modestring
ready_epochnumber | null
reasonstringfor down and quarantined
restore_generationinteger
state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed"
taskany
timingsobjectseconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total
updated_epochnumber
validityValidityvalid 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]}.
Request
curl -s "$DRUMS_URL/v1/slots/$COMPUTER_ID" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{id, state, episode, verdict: .validity.verdict, lease_expires_epoch}'
Response (from the verification run, trimmed)
{"id":"26ce04d4-50c1-478e-a771-dbe57bb93eb1","state":"ready","episode":1,"verdict":"pending","lease_expires_epoch":1793816168.91}

Close a computer

DELETE/gw/v1/slots/{id}scope computers:write

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.

Path parameters

idstringrequiredComputer UUID.

Response

The destroyed record (state: "destroyed").

Slot fields (21)Show
checkpointstringThe base image name or saved-state UUID this computer restores to.
closed_epochnumber | null
closed_reasonstring | nullWhy it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'.
costCost
created_epochnumber
episodeinteger
gatesobject
idstringThe computer's UUID (the id shown in the console).
last_errorstring | null
last_used_epochnumber | nullThe last request naming this slot.
leaseobject | null
lease_expires_epochnumber | nullWhen the lease runs out if the slot is not used again (null: no lease, or destroyed).
modestring
ready_epochnumber | null
reasonstringfor down and quarantined
restore_generationinteger
state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed"
taskany
timingsobjectseconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total
updated_epochnumber
validityValidityvalid 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.

Errors (besides the common ones)

409wrong_stateA console-created computer that has not started yet: deletion is requested and happens when it starts.
410env_goneThe computer never got a desktop, or its desktop is already gone from the host.
Request
curl -s -X DELETE "$DRUMS_URL/v1/slots/$COMPUTER_ID" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{id, state, closed_epoch}'
Response (from the verification run, trimmed)
{"id":"a5af0c19-efee-4c07-acaf-4e90a7d2dd58","state":"destroyed","closed_epoch":1793813681.21}

Episode

Act

POST/gw/v1/slots/{id}/stepscope computers:write

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.returncode is still HTTP 200; the SDK raises ActionFailed for it unless check=False.
  • screenshot: true adds screenshot_png_b64 to the reply.

Path parameters

idstringrequiredComputer UUID.

Body (JSON)

actionobjectrequired{"type": "pyautogui", "code"}, {"type": "execute", "command", "shell"}, {"type": "launch", "command", "shell"} or {"type": "sleep", "seconds"} (at most 60).
screenshotbooleanAlso return a PNG screenshot after the action (base64).

Response

{episode, result: {status, returncode, output, error}, validity, seconds, screenshot_png_b64}

StepResult fields (5)Show
episodeinteger
resultobjectguest server /execute reply
screenshot_png_b64string | null
secondsnumber
validityValidityvalid 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.

Errors (besides the common ones)

400invalid_requestThe body is not one of the action forms.
409wrong_stateThe computer is quarantined or not ready (details.state).
410env_goneThe computer is closed (closed_reason says why).
502bad_gatewayThe desktop's guest server did not answer in time.
Request
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)"}}'
Response (from the verification run, trimmed)
{"episode":1,"result":{"error":"","output":"","returncode":0,"status":"success"},
 "screenshot_png_b64":null,"seconds":0.187,"validity":{"verdict":"valid"}}

Screenshot

GET/gw/v1/slots/{id}/screenshotscope computers:read

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.

Path parameters

idstringrequiredComputer UUID.

Response

PNG bytes.

Errors (besides the common ones)

410env_goneThe computer is closed.
Request
curl -s "$DRUMS_URL/v1/slots/$COMPUTER_ID/screenshot" -H "Authorization: Bearer $DRUMS_TOKEN" -o screen.png && file screen.png
Response (from the verification run, trimmed)
screen.png: PNG image data, 1920 x 1080, 8-bit/color RGB, non-interlaced

Gate report

GET/gw/v1/slots/{id}/gatesscope computers:read

The current episode's gate report and verdict (pending, valid or invalid).

Python SDK: env.gates(), env.valid(), env.wait_valid()

  • The verdict is pending for about 5 s after create, reset or fork (the alive_after_resume gate).

Path parameters

idstringrequiredComputer UUID.

Response

{slot, episode, gates: {gates: [{gate, status, seconds, detail}]}, validity: {verdict, gates?}, cost}

Request
curl -s "$DRUMS_URL/v1/slots/$COMPUTER_ID/gates" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{verdict: .validity.verdict, gates: [.gates.gates[] | "\(.gate)=\(.status)"]}'
Response (from the verification run, trimmed)
{"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

POST/gw/v1/slots/{id}/verifyscope computers:write

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 score only when valid is true. On a quarantined computer the checker does not run: score is null and checker_skipped says why.
  • Some OSWorld checkers run a postconfig step (save a file, close a window) that changes the desktop.

Path parameters

idstringrequiredComputer UUID.

Body: none, or an empty JSON object {}.

Response

{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).

Errors (besides the common ones)

400invalid_requestThe computer has no task (create it with one, or load one with /task).
410env_goneThe computer is closed.
Request
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}'
Response (from the verification run, trimmed)
{"score":0,"valid":true,"invalid_reasons":[],"checker":"exact_match","seconds":0.09}

Reset

POST/gw/v1/slots/{id}/resetscope computers:write

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.

Path parameters

idstringrequiredComputer UUID.

Response

The computer record of the new episode.

Slot fields (21)Show
checkpointstringThe base image name or saved-state UUID this computer restores to.
closed_epochnumber | null
closed_reasonstring | nullWhy it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'.
costCost
created_epochnumber
episodeinteger
gatesobject
idstringThe computer's UUID (the id shown in the console).
last_errorstring | null
last_used_epochnumber | nullThe last request naming this slot.
leaseobject | null
lease_expires_epochnumber | nullWhen the lease runs out if the slot is not used again (null: no lease, or destroyed).
modestring
ready_epochnumber | null
reasonstringfor down and quarantined
restore_generationinteger
state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed"
taskany
timingsobjectseconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total
updated_epochnumber
validityValidityvalid 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`.

Errors (besides the common ones)

409wrong_stateThe computer is quarantined (release it first).
410env_goneThe computer is closed.
Request
curl -s -X POST "$DRUMS_URL/v1/slots/$COMPUTER_ID/reset" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{state, episode, verdict: .validity.verdict}'
Response (from the verification run, trimmed)
{"state":"ready","episode":2,"verdict":"pending"}

Load a task

POST/gw/v1/slots/{id}/taskscope computers:write

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.

Path parameters

idstringrequiredComputer UUID.

Body (JSON)

taskobjectrequiredAn OSWorld task JSON.

Response

The computer record.

Slot fields (21)Show
checkpointstringThe base image name or saved-state UUID this computer restores to.
closed_epochnumber | null
closed_reasonstring | nullWhy it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'.
costCost
created_epochnumber
episodeinteger
gatesobject
idstringThe computer's UUID (the id shown in the console).
last_errorstring | null
last_used_epochnumber | nullThe last request naming this slot.
leaseobject | null
lease_expires_epochnumber | nullWhen the lease runs out if the slot is not used again (null: no lease, or destroyed).
modestring
ready_epochnumber | null
reasonstringfor down and quarantined
restore_generationinteger
state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed"
taskany
timingsobjectseconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total
updated_epochnumber
validityValidityvalid 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).

Errors (besides the common ones)

410env_goneThe computer is closed.
422task_unsupportedThe task needs the agent itself to act as root.
422unsupported_runtimeThe task's setup failed; the computer is quarantined.
Request
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}'
Response (from the verification run, trimmed)
{"state":"ready","task":"e0df059f-28a6-4169-924f-b9623e7184cc"}

Computer metrics

GET/gw/v1/slots/{id}/metricsscope computers:read

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.

Path parameters

idstringrequiredComputer UUID.

Response

{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.

Request
curl -s "$DRUMS_URL/v1/slots/$COMPUTER_ID/metrics" -H "Authorization: Bearer $DRUMS_TOKEN" | jq '{state, lifetime_s, episodes: .usage.episodes}'
Response (from the verification run, trimmed)
{"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

POST/gw/v1/slots/{id}/capturescope computers:write

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 id is a snapshot UUID; the console lists it under Snapshots. id in 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_exceeded with details.limit: max_saved_states).
  • Recorded in the audit log as computer.snapshot.

Path parameters

idstringrequiredComputer UUID.

Body (JSON)

idstringDisplay name (at most 128 characters).
quiesce_timeout_snumberHow long to wait for a quiet process tree (0..300, default 30).
quiesce_window_snumberHow long the tree must stay quiet (0..60, default 1).
idle_timeout_snumberSaved-state lease: deleted after this many seconds with no computer made from it (default 1800).
ttl_snumberSaved-state lifetime once no computer uses it.

Response

The saved state's checkpoint record.

Checkpoint fields (14)Show
idstringA base image name (e.g. osworld-ubuntu) or a saved-state (snapshot) UUID.
captured_fromstring | nullThe computer UUID the saved state was taken from (null for base images).
created_epochnumber
expected_state_hashstring | null
provenanceobjectHashes of the runtime sources and image inspection the checkpoint was made with.
quiescenceobject | nullCapture readiness record: params, every attempt (wait, dump, checks, why a rejected dump was rejected).
stalebooleanTrue when the runtime cannot restore it (e.g. after a host reboot); creates from it fail.
stale_reasonstring | null
taskobject | nullThe 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_childrenbooleanDeleted 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.

Errors (besides the common ones)

403quota_exceededThe host's saved-state limit is reached.
409wrong_stateThe computer is not ready.
409not_quiescentThe process tree did not become quiet within the timeout. Nothing was captured; the computer is still ready.
Request
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}'
Response (from the verification run, trimmed)
{"id":"ac6feaf8-275e-4c39-adbd-11b5fe518273","captured_from":"a5af0c19-efee-4c07-acaf-4e90a7d2dd58",
 "expected_state_hash":"44f4a841…","seconds":2.185}

Fork

POST/gw/v1/slots/{id}/branchscope computers:write

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

  • n is 1..64 in the body and at most the host's max_fork_n; the children count against your live-computer limit.
  • Children get lifetimes like a create (ttl_s clamped to the workspace default and maximum).
  • delete_state_with_children: true deletes the fork's saved state when its last child closes (the SDK's keep_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} in children.
  • Recorded in the audit log as computer.fork.

Path parameters

idstringrequiredComputer UUID.

Body (JSON)

nintegerrequiredNumber of copies, 1..64 (and at most the host's limit).
delete_state_with_childrenbooleanDelete the saved state when the last child closes.
ttl_snumberChildren's lifetime (clamped).
idle_timeout_snumberChildren's idle timeout.
quiesce_timeout_snumber0..300.
quiesce_window_snumber0..60.
state_idle_timeout_snumberThe saved state's idle lease.
state_ttl_snumberThe saved state's lifetime once unused.

Response

{checkpoint, state, children: [computer record | {error}], capture_s, children_ready_s, seconds, delete_state_with_children}

BranchResult fields (7)Show
capture_snumber
checkpointCheckpoint
childrenSlot[]
children_ready_snumber
delete_state_with_childrenboolean
secondsnumber
statestringThe 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.

Errors (besides the common ones)

400invalid_requestn is missing or not 1..64.
403quota_exceededn 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_quiescentThe process tree did not become quiet within the timeout.
429overloadedThe host is full.
Request
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}]}'
Response (from the verification run, trimmed)
{"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

POST/gw/v1/slots/{id}/quiescentscope computers:write

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)

Path parameters

idstringrequiredComputer UUID.

Body (JSON)

timeout_snumber0..600 (default 30).
window_snumber0..60 (default 1).

Response

{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.

Errors (besides the common ones)

409not_quiescentNot quiet within the timeout (unstable lists what kept changing).
Request
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}'
Response (from the verification run, trimmed)
{"quiescent":true,"waited_s":1.013,"processes":53}

Quarantine

Quarantine

POST/gw/v1/slots/{id}/quarantinescope computers:write

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.

Path parameters

idstringrequiredComputer UUID.

Body (JSON)

reasonstringAt most 200 characters; shown in invalid_reasons as quarantined: <reason>.

Response

The record, state: "quarantined".

Slot fields (21)Show
checkpointstringThe base image name or saved-state UUID this computer restores to.
closed_epochnumber | null
closed_reasonstring | nullWhy it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'.
costCost
created_epochnumber
episodeinteger
gatesobject
idstringThe computer's UUID (the id shown in the console).
last_errorstring | null
last_used_epochnumber | nullThe last request naming this slot.
leaseobject | null
lease_expires_epochnumber | nullWhen the lease runs out if the slot is not used again (null: no lease, or destroyed).
modestring
ready_epochnumber | null
reasonstringfor down and quarantined
restore_generationinteger
state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed"
taskany
timingsobjectseconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total
updated_epochnumber
validityValidityvalid 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.

Errors (besides the common ones)

410env_goneThe computer is closed.
Request
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}'
Response (from the verification run, trimmed)
{"state":"quarantined","reason":"harness check failed","validity":{"gates":["not_quarantined"],"verdict":"invalid"}}

Release

POST/gw/v1/slots/{id}/releasescope computers:write

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.

Path parameters

idstringrequiredComputer UUID.

Body: none, or an empty JSON object {}.

Response

The record, state: "down".

Slot fields (21)Show
checkpointstringThe base image name or saved-state UUID this computer restores to.
closed_epochnumber | null
closed_reasonstring | nullWhy it was destroyed when not by a client's DELETE, e.g. 'lease: idle 1800 s'.
costCost
created_epochnumber
episodeinteger
gatesobject
idstringThe computer's UUID (the id shown in the console).
last_errorstring | null
last_used_epochnumber | nullThe last request naming this slot.
leaseobject | null
lease_expires_epochnumber | nullWhen the lease runs out if the slot is not used again (null: no lease, or destroyed).
modestring
ready_epochnumber | null
reasonstringfor down and quarantined
restore_generationinteger
state"creating" | "ready" | "resetting" | "capturing" | "down" | "quarantined" | "destroying" | "destroyed"
taskany
timingsobjectseconds per phase: kill, upper, prepare, restore, criu_clock, gates_kernel, guest_ready, ready, hash_isolation, task_setup, sync_total, total
updated_epochnumber
validityValidityvalid 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.

Errors (besides the common ones)

409wrong_stateThe computer is not quarantined.
410env_goneThe computer is closed.
Request
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}'
Response (from the verification run, trimmed)
{"state":"down","reason":"released from quarantine: harness check failed"}

Runs, keys and audit

List runs

GET/gw/v1/runsscope runs:read

The workspace's runs (evaluations and scripted tests), newest first. Scope runs:read.

  • Query: kind (evaluation or scripted_test), status (a run status), limit (1 to 200, default 50), cursor (the next_cursor of 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.

Query parameters

kind"evaluation" | "scripted_test"evaluation or scripted_test
statusstringa run status
limitinteger1..200 (default 50) Range 1..200.
cursorstringnext_cursor of the previous page

Response

{items: [Run...], next_cursor}

Runtime notes (drumsd)

Runs of the workspace (evaluations and scripted tests), newest first. Scope runs:read.

Errors (besides the common ones)

400invalid_requestkind is not a run kind, status not a run status, or limit is out of range.
Request
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

POST/gw/v1/runsscope runs:write

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 (evaluation or scripted_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-Key header: 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.

Body (JSON)

namestringrequiredLowercase letters, digits and dashes, up to 64.
kindstringrequiredevaluation (model agent) or scripted_test (scripted agent).
task_set_idstringrequiredA task set of the workspace or a platform set.
task_idsstring[]requiredUnique ids from that set, 1 to 366.
template_version_idstringrequiredA 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?}.
executionobjectrequiredparallelism 1..8, fork_n 1..8 (default 1), prefix_steps 0..20, max_steps 1..50, rounds 1..50.
budgetobjectrequiredusd (above 0), max_duration_s (60..86400), verified_target (optional).
retry_of_run_idstringAn ended run this one retries.

Response

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.

Errors (besides the common ones)

400invalid_requestThe spec does not parse, or a fork has no prefix step.
403quota_exceededParallelism or copies per fork above the workspace limit, too many computers at once, or too many runs already active.
403budget_exceededThe budget is above the spend left in this period.
404not_foundThe task set, a task, the template version or the secret reference is not in this workspace.
409wrong_stateThe 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_runtimeA task is not supported, the template version's image is not the host's, or the run kind is not offered in v1.
429overloadedThe compute pool is draining; new runs cannot start right now (Retry-After).
Request
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

GET/gw/v1/runs/{id}scope runs:read

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} and events (newest first: seq, type, event_at, attempt_id, payload).
  • A run of another workspace is 404, never 403.

Path parameters

idstringrequiredBase image name or saved-state UUID.

Response

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.

Errors (besides the common ones)

404not_foundNot a run of your workspace.
Request
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

GET/gw/v1/api-keysscope any valid key

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.

Response

{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.

Request
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

POST/gw/v1/api-keysscope any valid key

Mints a key owned by the calling key's owner. The plaintext key is in this reply once and nowhere else.

  • key in 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).

Body (JSON)

namestringrequired1 to 80 characters.
scopesstring[]requiredUnique, at least one of computers:read, computers:write, runs:read, runs:write, templates:read, templates:write, usage:read, audit:read.
expires_in_daysinteger1 to 365; absent means no expiry.

Response

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.

Errors (besides the common ones)

400invalid_requestEmpty name, no scopes, an unknown scope, or an expiry out of range.
403forbiddenThe calling key is a member's key, or asks for a scope it does not have itself.
Request
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

DELETE/gw/v1/api-keys/{id}scope any valid 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_at with already: true.
  • Recorded in the audit log as api_key.revoke.

Path parameters

idstringrequiredBase image name or saved-state UUID.

Response

{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).

Errors (besides the common ones)

400invalid_requestThe id is the calling key's own id.
403forbiddenThe calling key is a member's key.
404not_foundNot a key of your workspace.
Request
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

GET/gw/v1/auditscope audit:read

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 as computer.create, or a prefix such as mcp, which matches every mcp.<tool> row), limit (1 to 200, default 50), cursor (the next_cursor of 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.

Query parameters

actionstringaction or action prefix, e.g. mcp or computer.create
limitinteger1..200 (default 50) Range 1..200.
cursorstringnext_cursor of the previous page

Response

{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.

Errors (besides the common ones)

400invalid_requestaction is not a dotted lower-case name, or limit is out of range.
Request
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.