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.

5 min readSume
All posts

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.

List parameters and responses for schedule runs (read 2026-10-03)
ItemValue
limit1-100, default 50
cursorPass back next_cursor; opaque
has_morefalse on the last page
Bad cursor400 invalid_request
Scopeactions: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

All Developers posts

Written by Sume