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.

4 min readSume
All posts

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.

Format run routes, per the Sume docs (read 2026-10-02)
RouteWorks?Use it for
GET /v1/formats/{handle}/{slug}/runsYesNewest-first page of runs for one Format
GET /v1/formats/{format_id}/runsYesThe same list, by opaque Format id
GET /v1/format-runs/{run_id}YesOne run receipt at any status
GET /v1/format-runsNoThere is no cross-Format list
GET /v1/formats/{handle}/{slug}/runs/{run_id}NoThe 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_more as the stop condition, not an empty data array.
  • Do not persist cursors across days; store run ids instead.
  • Handle 429 by waiting retry-after, and 503 as transient.
  • Use a key with formats:read; list and read need nothing more.

Sources

Related posts

More in Formats

All Formats posts

Written by Sume