List all Sume Format runs: no cross-Format endpoint, page per Format
GET /v1/format-runs does not exist. List runs per Format with limit and next_cursor, or keep your own index of the run ids you stored at create.

There is no endpoint that lists every Format run in a workspace. The runs docs say so directly: GET /v1/format-runs has no cross-Format list. To enumerate runs, list them per Format with GET /v1/formats/{handle}/{slug}/runs, or keep your own index keyed by the data.id you stored when you created each run.
The route that does exist
Runs are listed on the Format path and read on the run path. The list is newest first, limit is 1 to 100 and defaults to 20, and GET /v1/formats/{format_id}/runs is the same list addressed by the opaque Format id.
| Route | Works? | Use it for |
|---|---|---|
| GET /v1/formats/{handle}/{slug}/runs | Yes | Newest-first page of runs for one Format |
| GET /v1/formats/{format_id}/runs | Yes | The same list, by opaque Format id |
| GET /v1/format-runs/{run_id} | Yes | One run receipt at any status |
| GET /v1/format-runs | No | There is no cross-Format list |
| GET /v1/formats/{handle}/{slug}/runs/{run_id} | No | The Format path only creates and lists |
Paging with next_cursor
The response is a page. Pass next_cursor back as cursor until has_more is false. The cursor is opaque and keyset over (created_at, id), so runs created while you page do not shift rows between pages. A cursor that did not come from Sume is 400 invalid_request, so never build one yourself.
A Format that has never been run over the API returns an empty list, not a 404. That makes an empty page a safe first-run state rather than an error to special-case.
import json
import os
import urllib.parse
import urllib.request
BASE = "https://api.sume.com/v1/formats/acme/live-commerce/runs"
KEY = os.environ["SUME_API_KEY"]
def get(url):
req = urllib.request.Request(url, headers={"Authorization": f"Bearer {KEY}"})
with urllib.request.urlopen(req) as resp:
return json.load(resp)
def all_runs():
cursor = None
while True:
query = {"limit": 100}
if cursor:
query["cursor"] = cursor
page = get(f"{BASE}?{urllib.parse.urlencode(query)}")
yield from page["data"]
if not page["has_more"]:
return
cursor = page["next_cursor"]
for run in all_runs():
print(run["id"], run["status"])
Covering several Formats
If you run more than one Format, loop over the Formats you own and list each. GET /v1/formats returns your workspace's Formats with the same keyset paging, so the outer loop is also a cursor loop.
The cheaper pattern is the one the docs recommend: store the data.id from every create call in your own table, next to your order or job id. Then a dashboard reads your table and fetches receipts by id, instead of scanning history. Pair it with an Idempotency-Key per business event so a retry returns the same run rather than a second one.
What a listed run gives you
Each entry is a run receipt, so you can filter in code on status, usage or error. Read usage.debited_usd_micros when you want cost; billable_amount_usd_micros is the generation spend counted against the run's cap and excludes the agent's own LLM turn.
Reads draw on the read budget, which is separate from and far larger than the write budget. A nightly sweep over a few hundred runs is well inside it, but a loop that re-lists everything every second is not a good use of it.
Checks before you ship
Four habits keep a history sweep reliable. They matter most when the sweep runs unattended on a schedule.
- Treat
has_moreas the stop condition, not an emptydataarray. - Do not persist cursors across days; store run ids instead.
- Handle
429by waitingretry-after, and503as transient. - Use a key with
formats:read; list and read need nothing more.
Sources
Related posts
More in Formats
- OpenAI response_format json_schema to a Sume output_schema
Moving a json_schema from OpenAI structured outputs to a Sume Format run: the field name, what transfers, no JSON mode, and why output comes once, post-run.
- Sume agent_reported_failure: the three reasons and what to do
agent_reported_failure on a Sume run means the run's own receipt said it did not deliver. Its details.reason tells you which of three cases it was.
- Sume primary_output_missing: schema satisfied, run still failed
A Sume run can match your output_schema and still end failed with primary_output_missing. It means the key named in primary_output_key had no value.
- Sume output_extraction_failed: the run stays completed, reread it
output_extraction_failed with reason harvest_unavailable is transient. The Sume run stays completed and fills in on your next read; retry only if it persists.
Written by Sume