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, skipped, failed, success |
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
}
]
}
| 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, skipped, failed, or success |
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 |
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'
# 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
fromwith 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
countas your truncation check when exporting — if it grows while you page, new runs arrived; overlap your nextfromwindow slightly. - Revoke tokens you stop using on the Access Tokens page.
Related
- Digest API, the rollup view of the same data — totals, success rates, and what's failing or stopped.
- Report Health Digest and Personal Digest, the email versions.