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
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
- Early Data Preparation, the schedule option behind the
waitingstatus and the gate fields. - Digest API, the rollup view of the same data — totals, success rates, and what's failing or stopped.
- Reports API, the inventory of the reports themselves — schedule, destinations, formats, and full YAML configuration.
- Report Health Digest and Personal Digest, the email versions.