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.
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.

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, and errors.
  • Digest API — run-health rollups over a time window.