Runs & history

GET /api/v1/runslists runs for your API key's workspace, newest first — use it to build dashboards or audit what your integration executed. Accepts limit (1–100, default 50), cursor pagination via starting_after (see Pagination), and filters: status, runner, routing, created_after / created_before, parent_id, exclude_children.

# First page (newest first)
curl -s "$BANANA_PEEL_BASE/api/v1/runs?limit=50" \
  -H "Authorization: Bearer $BANANA_PEEL_API_KEY"
# → { "object": "list", "data": [ /* banana_peel.run/v1 rows */ ],
#     "has_more": true, "first_id": "run_…", "last_id": "run_…",
#     "next_starting_after": "run_…" }

# Next page — resume strictly after the cursor
curl -s "$BANANA_PEEL_BASE/api/v1/runs?limit=50&starting_after=run_abc123" \
  -H "Authorization: Bearer $BANANA_PEEL_API_KEY"

# Filters combine (AND): failed smart runs on steel from August
curl -s "$BANANA_PEEL_BASE/api/v1/runs?status=failed&routing=smart&runner=steel\
&created_after=2026-08-01T00:00:00Z&created_before=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer $BANANA_PEEL_API_KEY"

Field reference

GET/api/v1/runs (listRuns)

List runs for the API key owner

Parameters

FieldTypeReqDescription
limitquery: integer
default 50
starting_afterquery: stringRun id cursor — return runs strictly after it in the newest-first order. Unknown ids yield an empty terminal page.
created_afterquery: string (date-time)ISO 8601 — only runs created strictly after this instant.
created_beforequery: string (date-time)ISO 8601 — only runs created strictly before this instant.
statusquery: string
running | needs_human | succeeded | failed | blocked | empty | cancelled
Effective run status (post watchdog fix-up).
runnerquery: stringExecuted runner slug (exact match).
routingquery: string
smart | learning | learning-all | category | runner | chain
Routing class of the requested routing param: runner = pinned single runner, chain = runner array, category = SOC2/HIPAA/EU.
parent_idquery: stringOnly the sub-runs of one long task (lt_…). Implies exclude_children=false.
exclude_childrenquery: boolean
default true
DEFAULT true: long-task sub-runs are omitted from the top-level list so one fan-out does not bury user-initiated runs. Pass false to interleave them; they are always reachable via parent_id= or GET /v1/long-tasks/{id}/runs.

Response

FieldTypeReqDescription
object"list"yes
dataany[]yes
data[]objectCanonical banana_peel.run/v1 — superset over all runners. `runner` is the executed runner slug; `routing` is what you requested; `engine` is the execution backend when distinct.
data[].schema"banana_peel.run/v1"
data[].idstring
data[].statusstring
succeeded | failed | running | blocked | empty | needs_human | cancelled
data[].progressobjectLive in-flight visibility (only while status is in_progress / requires_action): runner attempts so far, current Learning batch, and runner spend to date. Superseded by routing_attempts on the terminal object.
data[].required_actionobjectMFA/OTP pause mirror (prompt/kind only; never the secret).
data[].modelstring
data[].routingany
data[].routing_notestring
data[].ranked_runnersstring[]
data[].parent_idstringPresent on long-task sub-runs only — the parent long task (lt_…). See GET /v1/long-tasks/{id}/runs.
data[].entity_idstringPresent on long-task sub-runs — the manifest entity this run serves (__enumeration__ for the enumeration step).
data[].rolestring
enumeration | entity_fetch | verification
Role of a long-task sub-run within its parent. verification = a sampled spot-check confirming randomly chosen claimed entity ids exist.
data[].routing_summaryobjectCompact "why was it routed there" summary — present on every brain-routed run (routing: "smart"/category and forced-runner decisions the brain recorded; absent on pinned/chain/learning runs, which have no decision to explain). In-flight polls carry the honest base (planned primary + decision reason); the terminal object adds per-attempt fault attribution, the policy version, and a budget-widening echo. The `summary` sentence is redacted before it leaves the API. The same object is embedded in response.completed / response.failed webhook payloads (data.banana_peel.routing_summary). For the full decision card (candidate EU table, site defense, widening steps + stop reasons) follow `explanation` — GET /v1/responses/{id}/explanation.
data[].runnerstringExecuted runner slug
data[].enginestringExecution backend behind the runner when distinct (e.g. browser-use-cloud)
data[].urlstring
data[].outputanyRun result in canonical shape, identical across runners: a plain string for freeform tasks, or a structured object/array when the runner returned real JSON (or output_schema was requested). Normalized at a single choke point before persist + envelope build: char-exploded strings ({"0":"T","1":"h",…}) are reassembled and JSON-encoded strings unwrapped, so the shape never varies by runner. With an output_schema this is a HARD CONTRACT: a terminal success populates exactly the schema keys, or the run is failed with error.code schema_violation.
data[].answerstringGUARANTEED READ PATH (stable default envelope): best-effort distilled answer string, always present as a key. Runner outputs name their keys differently run to run (current_outstanding_balance vs result vs prose-in-summary) — `answer` is the stable projection, so clients without an output_schema never chase keys. Null exactly when answer_present is false. For legitimately empty results the answer states the empty result (e.g. the "No Order History Found" evidence); for download tasks it lists the delivered files.
data[].answer_presentbooleanDelivery-verdict bit: was the REQUESTED ANSWER actually delivered? True on every genuine success — including a legitimate empty/zero result ("0 orders" IS the answer; empty-result ≠ failure). False on failed/blocked runs and on hollow completions (output that only narrates login/click actions without the requested value — those are status failed, never billed-and-claimed-successful). Meaningful on terminal states; false while still running.
data[].evidenceobjectEvidence trail backing `answer`: verdict reason, final URL, artifact names.
data[].errorobject
data[].qualityobjectDelivery verdict from the honesty layer (deterministic checks + QA grader): did the run deliver the requested answer? Terminal `status` follows this verdict — not the runner’s mechanical exit code — and the routing scoreboard/brain learn from the same verdict-based labels.
data[].deliverablesobjectDelivery verdict for file-deliverable tasks: claimed downloads vs files actually captured as retrievable artifacts. Null for text-only tasks. A run that claims downloads but delivers zero captured files is demoted to failed (verdict not_delivered) — in-session download URLs expire with the browser session and are not deliverables. In structured output, `download_succeeded` is renamed `download_triggered` (the in-session click) with a sibling `file_delivered` boolean (actual capture).
data[].block_reasonstring
data[].timing_msnumber
data[].costobject
data[].normalizationobjectOutput normalization echo — present whenever normalization was requested. `applied: true` means the reshape pass executed (and the +$0.10 add-on was billed); `applied: false` includes a short `reason` and `addon_usd: 0` (never charged for a pass that did not run, and never charged when the output_schema contract was violated — see error.code schema_violation).
data[].custodyobjectCustody enforcement echo — present whenever a credential custody policy applied to this run (vault credential and/or request `custody`). The runner candidate set was hard-filtered to `allowed_runners` BEFORE routing; no fallback, race, wave, or learning probe left that set. Persisted on the run record for audit.
data[].sessionobjectMetadata about the browser this run used (live view, replay, CDP connect). A record of the run’s browser — not a standalone session you manage. Unused fields are null.
data[].live_viewstring
data[].replay_urlstring
data[].screenshotstring
data[].screenshot_unavailable_reasonstring
data[].artifactsany[]
data[].stepsobject[]
data[].fallback_fromstring[]
data[].act_scriptarray
data[].created_atstring (date-time)
data[].finished_atstring (date-time)
has_morebooleanyes
first_idstring
last_idstring
next_starting_afterstringPresent when has_more — cursor for the next page. Usually equals last_id; after a filtered scan window it is the last scanned run id (data may even be empty). Always pass it (not last_id) when present.

HTTP statuses

  • 200 — Run list page (canonical banana_peel.run/v1 rows)
  • 400 — Invalid filter (invalid_list_filter)
  • 401 — Invalid API key

Generated from OpenAPI. Do not hand-maintain this table.

Filters

  • status= — one of running, needs_human, succeeded, failed, blocked, empty, cancelled. Matches the effective status the API returns: a stored-running ghost past its time contract filters as failed, exactly as it is listed.
  • runner= — exact executed-runner slug (e.g. steel), i.e. the runner that actually ran the task.
  • routing= — the routing class of the request: smart | learning | learning-all | category (SOC2 / HIPAA / EU) | runner (pinned single runner) | chain (runner array). For one specific pinned slug, combine with runner=.
  • created_after / created_before — ISO 8601 instants, strict bounds. Invalid values fail with a 400 invalid_list_filter; nothing is silently ignored.
  • parent_id= — only the sub-runs of one long task (lt_…). Sub-runs carry parent_id, entity_id, and role (enumeration | entity_fetch) on their list rows and response envelopes.
  • exclude_children=defaults to true: the top-level list omits long-task sub-runs so one fan-out doesn't bury your user-initiated runs (this default changed when long tasks shipped — see the changelog). Sub-runs are never gone: pass exclude_children=false to interleave them, filter with parent_id=, or list a task's children directly with GET /api/v1/long-tasks/:id/runs.

Filters are honest post-filters over the indexed newest-first scan. When a bounded scan window ends before a filtered page fills, the response still carries has_more: true and next_starting_after (the last scanned run id — data may even be empty), so pagination always makes forward progress and never silently drops history. Always resume with next_starting_after when it is present.

What each run includes

  • The canonical banana_peel.run/v1 fields: the routing you requested, the runner that executed, status, cost, timing, session links, and artifact metadata.
  • The same rows back the console Runs page, where you can inspect replays and screenshots.

To fetch one run in its OpenAI Responses shape, use GET /api/v1/responses/:id. To purge copied screenshots and artifacts immediately, DELETE /api/v1/responses/:id. Copied documents expire after 7 days by default — see Data handling & retention.

Command Palette

Search for a command to run...