Flow · 10 steps

Build, save and run a report

Save a report, run it, and trace a figure back to what was recorded.

Reports read a copy of every service's records, never the live ones. This walk saves a report under an rpt_ id you make (its idempotency key), shows a retry changes nothing, replaces it, runs it, follows a cell back to its facts, and reads how up to date the copy is. A new organization's figures are zero: the walk shows how it works, not data.

Services involved

A filled dot is a read; a ring is a call that changes something.

  • Reporting Manager: https://reporting.getmanifold.io

Every call carries Authorization: Bearer $MANIFOLD_TOKEN unless its step says otherwise (getting a token). Run a flow in your sandbox, not your live organization: see Before you start.

1 Discover what can be reported on

The entity graph, filtered server-side to the caller's jobs: the reportable entities with their labelled dimensions and measures, and the declared edges between them. A builder driven by this answer can only offer what a save or a run would accept — a field your jobs may not see is simply not in the model.

GET /model Reporting Manager

What the answer holds: the operation's example.

2 Save a report

The client mints the rpt_ id: it is the document's identity and the idempotency key in one. The definition is validated against the entity graph with the caller's jobs before anything is written — a field your jobs may not see refuses the save exactly as it would refuse the run.

POST /reports Reporting Manager

Request · application/json
{
  "reportId": "rpt_01a1586d-1d4c-7444-8444-444444444444",
  "name": "Units moved by SKU",
  "definition": {
    "entity": "stockMovement",
    "hops": [
      "sku"
    ],
    "groupBy": [
      {
        "field": "sku"
      }
    ],
    "measures": [
      "quantity"
    ],
    "period": {
      "_tag": "preset",
      "preset": "30d"
    }
  }
}

Answers 201. What the answer holds: the operation's example.

Records reporting.report-saved.

3 Retry the save

The same reportId again — even with a different name — is acknowledged with the originally saved report and appends nothing. The empty appends here is an assertion the runner holds against the journal: a second fact would fail this step.

POST /reports Reporting Manager

Request · application/json
{
  "reportId": "rpt_01a1586d-1d4c-7444-8444-444444444444",
  "name": "A name the retry does not get to set",
  "definition": {
    "entity": "stockMovement",
    "hops": [
      "sku"
    ],
    "groupBy": [
      {
        "field": "sku"
      }
    ],
    "measures": [
      "quantity"
    ],
    "period": {
      "_tag": "preset",
      "preset": "30d"
    }
  }
}

Answers 201. What the answer holds: the operation's example.

Records nothing.

4 Rename it

A replace is idempotent by content: this one changes the name, so it appends the fact with the report as it reads afterwards. Sending the identical body again would append nothing.

PUT /reports/rpt_01a1586d-1d4c-7444-8444-444444444444 Reporting Manager

Request · application/json
{
  "name": "Units by SKU, last 30 days",
  "definition": {
    "entity": "stockMovement",
    "hops": [
      "sku"
    ],
    "groupBy": [
      {
        "field": "sku"
      }
    ],
    "measures": [
      "quantity"
    ],
    "period": {
      "_tag": "preset",
      "preset": "30d"
    }
  }
}

What the answer holds: the operation's example.

Records reporting.report-saved.

5 The library

Lists the saved reports whose every referenced field the caller's jobs may see — a ledger report is simply absent for someone without the account job.

GET /reports Reporting Manager

What the answer holds: the operation's example.

6 Run it

The reference interpreter executes the saved definition over the read model and answers the grouped rows plus asOf — the fold's per-source watermarks at the moment of the read, so a consumer can tell "zero" from "not folded yet". A run appends nothing: it is a pure query, and the runner checks that silence too.

POST /report-runs Reporting Manager

Request · application/json
{
  "reportId": "rpt_01a1586d-1d4c-7444-8444-444444444444"
}

What the answer holds: the operation's example.

Records nothing.

7 Drill a cell

Any leaf cell resolves back to the distinct fact rows that summed into it — each with its stable row id and the journal evt_ ids it was folded from. On this new organization the cell is empty, and the drill says so with zero rows rather than an error.

GET /report-runs/drill?reportId=rpt_01a1586d-1d4c-7444-8444-444444444444&measure=quantity&key=%5B%22WIDGET-1%22%5D Reporting Manager

What the answer holds: the operation's example.

8 How fresh are the numbers

The operational readout behind asOf: per-source replication-slot health and the fold's watermarks with the last run's outcome. Needs the account or admin job — freshness of financial figures is a finance concern.

GET /replica/status Reporting Manager

What the answer holds: the operation's example.

9 The journal shows exactly two saves

The report library is journalled like everything else: the create and the rename are reporting.report-saved facts with the house envelope, and the acknowledged retry between them is nowhere.

GET /events?since=0 Reporting Manager

What the answer holds: the operation's example.

10 Where the journal stands now

The head of the report library's journal at this moment — the last sequence recorded by the database's clock. A replay with upTo set to it reads the library exactly as it stood, whatever is saved after.

GET /events/head?at=2026-11-07T12%3A39%3A42.911Z Reporting Manager

What the answer holds: the operation's example.

What a recorded fact is: Facts and the journal. Why a repeated step changes nothing: Idempotency and retries.