The Run History API tells you how your reports ran. This endpoint tells you what your reports are: one record per report with its uid, name, type, schedule, delivery destinations, output formats, and the databases it queries — plus a link to the report's full YAML configuration. Use it to keep an external inventory (a CMDB, HubSpot, a monitoring project, your warehouse) in sync without exporting YAML files by hand.

Authentication is the same one-token flow as the other APIs: 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 inventory endpoint

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

All parameters are optional:

Parameter Meaning
type Only reports of one type: default (report), notebook, tableau_embed, or agent_run
active true for reports with an active schedule, false for the rest
name Case-insensitive name contains
page Page number, starting at 0
page_size Rows per page, 1 to 500. Default 100.

Invalid input returns a 422 with a message saying what's wrong.

What comes back

{
  "count": 143,
  "page": 0,
  "page_size": 100,
  "result": [
    {
      "uid": "pjoVBM6oYP",
      "name": "Weekly KPI Report",
      "type": "default",
      "active": true,
      "schedule": {
        "active": true,
        "rrule": "DTSTART:20260101T090000\nRRULE:FREQ=WEEKLY;BYDAY=MO",
        "next_run": "2026-08-17T09:00:00Z",
        "last_run": "2026-08-10T09:00:00Z"
      },
      "webhook_active": false,
      "destinations": ["email", "slack"],
      "output_formats": ["pdf", "xlsx"],
      "databases": [
        {"uid": "aK3mZpQ2vX", "name": "Snowflake Prod"}
      ],
      "folder": ["Clients", "Acme"],
      "tags": ["kpi", "weekly"],
      "created_by": "jane@acme.com",
      "created_at": "2025-11-03T14:22:10Z",
      "updated_at": "2026-08-01T08:15:44Z",
      "config_url": "/api/v1/report/pjoVBM6oYP/config"
    }
  ]
}
Field Meaning
uid The report's stable identifier — the same one in the report's URL in the app. Use it as your sync key.
type default (report), notebook, tableau_embed, or agent_run
active Whether the report's schedule is switched on. false also for reports that were never scheduled — check schedule to tell the two apart.
schedule The schedule as an iCalendar RRULE, with the next and last scheduled run (UTC). null when the report has never had a schedule. next_run is always the delivery time, also for schedules with early data preparation.
webhook_active Whether the report can be triggered by its webhook URL
destinations Where the report delivers: email, slack, sftp, webhook, aws_s3, gcp_bucket, azure_storage_blob, gdrive
output_formats File formats the report produces: csv, xlsx, pdf, png, pptx, html_table
databases The database connections the report's SQL queries use. name is null if the connection was deleted.
folder Folder path in the app, root first
created_by Email of the report's creator
config_url Path to the report's full YAML configuration (below)

Rows are ordered newest first. count is the total matching your filters, so count > (page + 1) * page_size means there are more pages.

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

Fetching a report's full configuration

Each row's config_url points at the report's complete configuration — the same YAML the in-app export produces, covering every block with its SQL, delivery settings, recipients, schedule, and webhook:

curl -s "https://pushmetrics.io/api/v1/report/pjoVBM6oYP/config" \
  -H "Authorization: Bearer $JWT"

The response is text/plain YAML, not JSON. It's the source-of-truth format PushMetrics itself uses for git sync and publishing, so anything configured on the report is in there.

Early data preparation on a schedule

A schedule can ask PushMetrics to start generating the report up to 45 minutes before the scheduled time and hold the output until then (the Allow early data preparation toggle, explained in Early Data Preparation). The setting lives on the schedule itself and is exposed by the schedule endpoint, not by the inventory row or the YAML configuration.

Every schedule you can see is one row of GET /api/v1/workflow/, with the report it belongs to nested under report:

# The schedule of one report, by report uid
curl -s "https://pushmetrics.io/api/v1/workflow/?q=(page_size:500)" \
  -H "Authorization: Bearer $JWT" \
  | jq '.result[] | select(.report.uid == "pjoVBM6oYP")
        | {id, active, rrule, next_run, early_prep_minutes}'
Field Meaning
early_prep_minutes How many minutes before the scheduled time generation may start. null or 0 means the option is off and the run starts at the scheduled time.

To change it, PUT the schedule by its id. Only the fields you send are changed:

# Start preparing 20 minutes early
curl -s -X PUT "https://pushmetrics.io/api/v1/workflow/1234" \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"early_prep_minutes": 20}'

# Switch it off again
curl -s -X PUT "https://pushmetrics.io/api/v1/workflow/1234" \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"early_prep_minutes": null}'

The value must be a whole number from 0 to 45; anything else, including 46, -1, 2.5 or true, is rejected with a 422 that names the problem. POST /api/v1/workflow/ accepts the same field when creating a schedule. The response echoes early_prep_minutes back, and next_run stays the delivery time: the early start never moves the schedule itself.

Useful one-liners

BASE="https://pushmetrics.io/api/v1/report"

# The whole inventory as a CSV: uid, name, active, destinations
curl -s "$BASE/inventory?page_size=500" -H "Authorization: Bearer $JWT" \
  | jq -r '.result[] | [.uid, .name, .active, (.destinations | join("+"))] | @csv'

# Every actively scheduled report with its next run
curl -s "$BASE/inventory?active=true&page_size=500" -H "Authorization: Bearer $JWT" \
  | jq -r '.result[] | [.schedule.next_run, .name] | @tsv' | sort

# Dump every report's YAML into files named by uid
for uid in $(curl -s "$BASE/inventory?page_size=500" -H "Authorization: Bearer $JWT" \
             | jq -r '.result[].uid'); do
  curl -s "$BASE/$uid/config" -H "Authorization: Bearer $JWT" > "$uid.yaml"
done

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_reports(jwt, **params):
    """Yield every report matching the filters, walking the pages."""
    page = 0
    while True:
        r = requests.get(f"{BASE}/report/inventory",
                         params={**params, "page": page, "page_size": 500},
                         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

def fetch_yaml(jwt, uid):
    r = requests.get(f"{BASE}/report/{uid}/config",
                     headers={"Authorization": f"Bearer {jwt}"})
    r.raise_for_status()
    return r.text

jwt = get_jwt()
for report in fetch_reports(jwt):
    print(f"{report['uid']}  {report['name']}  → {report['destinations']}")
    # config = fetch_yaml(jwt, report["uid"])   # when you need the full detail

Good practices

  • Key your sync on uid. It never changes, survives renames and moves, and matches the report's URL in the app and the report_uid in the Run History API.
  • Use updated_at to skip unchanged reports — only fetch the YAML for rows whose updated_at moved since your last sync.
  • A daily sync is plenty. Report configurations change at human speed; the Run History API is the endpoint to poll more often.
  • Revoke tokens you stop using on the Access Tokens page.
  • Run History API — one record per execution of these reports, with status, duration, errors, and the delivery-gate fields of early-prepared runs.
  • Early Data Preparation — what the schedule option does and what users see in the run history.
  • Digest API — run-health rollups over a time window.