The Digest API gives you run health as rollups over a window. This endpoint gives you the raw material instead: one record per individual execution, with its timestamp, status, duration, and — for failed runs — the error message. Use it to feed your own monitoring, warehouse, or incident tooling.

Authentication is the same one-token flow as the Digest API: create a Personal Access Token under Settings → Access Tokens (/a/tokens), then exchange it for a short-lived access key. The Digest API guide walks through both steps; the short version:

JWT=$(curl -s -X POST https://pushmetrics.io/api/v1/security/exchange \
  -H "Authorization: Bearer pmpat_YOUR_TOKEN" | jq -r .access_token)

The endpoint

curl -s "https://pushmetrics.io/api/v1/noteflow_run/history?page_size=100" \
  -H "Authorization: Bearer $JWT"

All parameters are optional:

Parameter Meaning
report_uid Only runs of one report. The uid is the identifier in the report's URL in the app.
status One of pending, running, waiting, skipped, failed, success. waiting is a run whose data is prepared and whose delivery is held until its scheduled time (see Early Data Preparation).
from / to ISO-8601 bounds on when the run was created, in UTC. from is inclusive, to exclusive. Date-only values like 2026-07-01 work.
page Page number, starting at 0
page_size Rows per page, 1 to 1,000. Default 100.

Invalid input returns a 422 with a message saying what's wrong — a typo'd status or a malformed timestamp never fails silently.

What comes back

{
  "count": 72623,
  "page": 0,
  "page_size": 100,
  "result": [
    {
      "run_uid": "lD6XAqbB2K",
      "report_uid": "pjoVBM6oYP",
      "name": "Weekly KPI Report",
      "status": "success",
      "created_at": "2026-07-27T10:47:34Z",
      "started_at": "2026-07-27T10:47:35Z",
      "finished_at": "2026-07-27T10:47:36Z",
      "duration_s": 1.155,
      "error": null,
      "gate_type": null,
      "gate_opens_at": null,
      "gate_opened_at": null,
      "gate_opened_by": null,
      "delivered_late_by_s": null
    }
  ]
}
Field Meaning
run_uid The run's identifier; links to /a/log in the app
report_uid The report this run executed, null for report-less runs (e.g. some agent runs)
name The report's name at execution time
status pending, running, waiting, skipped, failed, success, or cancelled (the delivery of an early-prepared run was cancelled while it was waiting, see Early Data Preparation)
created_at When the run was queued (UTC)
started_at / finished_at When execution began and ended; null while pending
duration_s Execution time in seconds, null until finished
error The failure message, on failed runs only
gate_type time when the run was scheduled with early data preparation and its delivery was held until the scheduled time. manual is reserved for approvals, a planned feature. null for every other run.
gate_opens_at The scheduled delivery time the output is held for (UTC). Set on gated runs only.
gate_opened_at When delivery was actually released (UTC). null while the run is still waiting.
gate_opened_by Who released the delivery: scheduler when the hold was released automatically (on time, or as soon as late generation finished)
delivered_late_by_s Seconds between the scheduled delivery time and the end of generation. 0 when the report was ready in time; greater than 0 when generation overran the scheduled time and the report was delivered as soon as it was ready. null on ungated runs and while waiting.

Rows are ordered newest first. count is the total matching your filters, so count > (page + 1) * page_size means there are more pages. Retries are not listed as separate rows — a re-run stays attached to its original run, exactly as in the app's Execution Log.

You see the runs of reports shared with you; workspace admins see every run in the workspace. The token's workspace binding applies, same as everywhere else.

Useful one-liners

BASE="https://pushmetrics.io/api/v1/noteflow_run/history"

# Everything that failed this week, with the actual error
curl -s "$BASE?status=failed&from=2026-07-22" -H "Authorization: Bearer $JWT" \
  | jq -r '.result[] | [.created_at, .name, .error] | @tsv'

# Full run log for one report as a CSV
curl -s "$BASE?report_uid=pjoVBM6oYP&page_size=1000" -H "Authorization: Bearer $JWT" \
  | jq -r '.result[] | [.run_uid, .status, .started_at, .finished_at, .duration_s] | @csv'

# Reports that are prepared but not yet delivered, with their delivery time
curl -s "$BASE?status=waiting" -H "Authorization: Bearer $JWT" \
  | jq -r '.result[] | [.name, .gate_opens_at] | @tsv'

# Early-prepared runs that still missed their delivery time this week
curl -s "$BASE?from=2026-09-01&page_size=1000" -H "Authorization: Bearer $JWT" \
  | jq -r '.result[] | select(.delivered_late_by_s > 0)
           | [.gate_opens_at, .name, .delivered_late_by_s] | @tsv'

# Success rate over a window, computed your way
curl -s "$BASE?from=2026-07-01&to=2026-08-01&page_size=1000" -H "Authorization: Bearer $JWT" \
  | jq '[.result[].status] | group_by(.) | map({(.[0]): length}) | add'

A small Python client

import requests

BASE = "https://pushmetrics.io/api/v1"
PAT = "pmpat_..."   # from your secrets store

def get_jwt():
    r = requests.post(f"{BASE}/security/exchange",
                      headers={"Authorization": f"Bearer {PAT}"})
    r.raise_for_status()
    return r.json()["access_token"]

def fetch_runs(jwt, **params):
    """Yield every run matching the filters, walking the pages."""
    page = 0
    while True:
        r = requests.get(f"{BASE}/noteflow_run/history",
                         params={**params, "page": page, "page_size": 1000},
                         headers={"Authorization": f"Bearer {jwt}"})
        if r.status_code == 401:   # key expired mid-walk, get a fresh one
            jwt = get_jwt()
            continue
        r.raise_for_status()
        body = r.json()
        yield from body["result"]
        page += 1
        if page * body["page_size"] >= body["count"]:
            return

jwt = get_jwt()
for run in fetch_runs(jwt, status="failed", **{"from": "2026-07-01"}):
    print(f"{run['created_at']}  {run['name']}: {run['error']}")

Good practices

  • Sync incrementally. Pass from with your last sync time instead of re-walking the full history; runs are immutable once finished.
  • Poll at a sane cadence. Runs land at schedule granularity; every few minutes is plenty, and the digest endpoints are the better fit for once-a-day health summaries.
  • Treat count as your truncation check when exporting — if it grows while you page, new runs arrived; overlap your next from window slightly.
  • Revoke tokens you stop using on the Access Tokens page.