Paging Sume schedule runs: limit 1-100, next_cursor, has_more
List a Sume schedule's runs with limit 1-100 (default 50), then pass next_cursor back as cursor until has_more is false. A forged cursor is a 400. Python loop.

To read a Sume schedule's full run history, call GET /v1/actions/{action_id}/runs with limit from 1 to 100 (default 50), and keep passing next_cursor back as cursor until has_more is false. The cursor is opaque: one Sume did not mint is rejected with 400 invalid_request, so never construct or edit one. GET /v1/actions pages the same way.
This follows Runs and results and Scheduled, read on 2026-10-03. Reading needs a key with actions:read.
The page contract
Each response is a page of the form data, has_more, next_cursor. When there are no more pages has_more is false and next_cursor is null. Treat the cursor as a token to hand back verbatim, and stop on has_more, not on an empty page.
| Item | Value |
|---|---|
| limit | 1-100, default 50 |
| cursor | Pass back next_cursor; opaque |
| has_more | false on the last page |
| Bad cursor | 400 invalid_request |
| Scope | actions:read |
A loop that walks everything
The Python below pages through every run for one schedule and counts them by status. It reads the key and the id from the environment.
import os
import collections
import requests
BASE = 'https://api.sume.com/v1'
HEADERS = {'Authorization': 'Bearer ' + os.environ['SUME_API_KEY']}
def all_runs(action_id):
cursor = None
while True:
params = {'limit': 100}
if cursor:
params['cursor'] = cursor
r = requests.get(f'{BASE}/actions/{action_id}/runs',
headers=HEADERS, params=params, timeout=30)
r.raise_for_status()
page = r.json()
yield from page['data']
if not page['has_more']:
return
cursor = page['next_cursor']
counts = collections.Counter(
run['status'] for run in all_runs(os.environ['SUME_ACTION_ID']))
print(dict(counts))
Why cursors, not page numbers
Run history grows while you read it, so a numbered offset would shift under you. An opaque cursor lets the server keep its place. The practical rules are short: always pass next_cursor back exactly as received, never persist it as a bookmark you did not just receive, and expect 400 invalid_request if you hand back a value Sume did not mint.
limit has the same bounds on both list endpoints. A value outside 1-100 is not a way to get a bigger page, so for large histories keep limit at 100 and loop. A single run can also be read directly at GET /v1/actions/{action_id}/runs/{run_id}, which is the right call when you already hold an id from a receipt and do not need the list.
Reading the statuses you get back
Run statuses are queued, processing, completed, failed, canceled and skipped. A skipped run never ran because another run was active under the skip overlap policy. If a count of failed or skipped runs is rising, the schedule's cadence may be shorter than its typical run time.
Reading history is also the place to confirm the spend rules hold. The default generation spend cap on a schedule is $1.00 when unset, and a per-run override can only lower it. Pulling runs by status is a cheap way to see how often a schedule hits its limits without opening each thread.
Where this fits in a monitor
A daily health check can list recent runs with a small limit, look at the newest few statuses, and only walk the full history when something looks wrong. Keep the key read-only: listing needs actions:read, and nothing in this loop writes.
Set a request timeout on every call, as the loop above does, and let a non-2xx response raise. A silent failure in a monitor is worse than a loud one, because the dashboard then shows an old, healthy-looking count.
Sources
Related posts
More in Developers
- Sume schedule vanity URL: store the aut_ id, not handle/slug
A Sume schedule can be run at /v1/actions/{handle}/{slug}/runs, but renaming either part changes the path. Store the aut_ id; an old handle resolves 90 days.
- Sume SDK 429 retry-after is capped at 60 seconds: what follows
createSumeClient waits at most 60 seconds per retry, with 20 percent jitter and 2 retries by default. What that means for a long retry-after and a fix.
- Sume SDK error code unknown_error and a null requestId
When the response body is not a Sume error envelope, the SDK falls back to code unknown_error with no request id. What it means and what to log instead.
- Sume SDK returns {data, error}, not exceptions: an unwrap helper
Generated @sume-com/sdk operations resolve with data, error and response instead of throwing. Wrap them in an unwrap helper that throws a typed error.
Written by Sume