# reportRun

query · in the family [Reading your records](/reference/reading-your-records/)

## What it does

Run a report — group and total the last step’s records (count, sums, smallest, largest, average per currency) or list chosen columns of them, newest first.

Run ONE report synchronously, as you, inside your tenant: a report WRAPS a search plan (the SearchPlanInput grammar VERBATIM — the start, up to 3 hops, the limit; THE PLAN GATE and the budget rule of searchPlanRun apply unchanged: a plan that would examine more than 5000 records (measured KEYS ONLY before the first read), a step over 1000 matches, or a run past 8 s refuses BEFORE partials exist — a plan too large for this lane says so and names the other one: start it as a background report) and says WHAT TO SAY about the LAST step's records — EITHER `columns` (a PROJECTION: 1..24 fields of the terminal family — its declared filter roster plus the header leaves id · sysId · caption · status · createdAt · updatedAt; one row per record, newest first (updatedAt desc, id tiebreak), up to the plan's `limit`; a money column brings its currency column, a reference column brings its `<field>.caption`) OR `groupBy` + `measures` (an AGGREGATE: 1..4 keys — enum, reference, text, flag or bucketed date fields (a date key NEEDS a bucket: day · week · month · quarter · year) — and up to 8 measures over money or number fields (sum · min · max · avg; `count` is ALWAYS the first measure whether named or not); a money measure joins the row's currency to the group implicitly and renders decimals by the currency's exponent, so two currencies are never summed together; at most 1000 distinct groups — past it the run refuses and asks for a coarser bucket or a condition) — never both, never neither. An optional `sort` names an OUTPUT column (`totalMinor.sum`, `createdAt.month`, `count`, …). The answer: the report in words, the terminal family, THE OUTPUT PLAN (`columns` — name + kind; the CSV header in this order), the rows as ONE JSON array text (`rowsJson` — parse it; a row's keys are the column names), rowCount · groupCount (0 for a projection) · the records examined · the pre-read estimate · the elapsed ms, and `captionsOmitted` — the reference families whose `.caption` column was dropped because you may not list them (the id column stands; no refusal). Every refusal is VALIDATION/INVALID in the teaching voice — the legal fields named. 📊 THE REPORT NAMED ONE WAY — exactly one of spec (an ad hoc report) or reportId (a saved Report definition, run as YOU, its spec re-validated now): both or neither refuses VALIDATION/INVALID; an inactive or doomed definition refuses CONFLICT/REF_STATE naming its state. AS YOU: a family you may not list refuses the whole plan before any read; the tenant rides every key. A JSON null on an optional input field reads as absent. Requires authentication.

## What happens

Read-only and bounded: it runs as you, inside your organization, over records you may already list. A report that would read too much stops before it starts and tells you what to narrow; nothing is saved.

## Who may call it

Capability area: **Reading your records** — Looking up and listing the records of your organization.

- Owner
- System Administrator
- Manager
- Associate Manager
- Warehouse Associate
- Sales Associate
- An API key whose scope allows `api:reportRun`

## Arguments

| Name | Type | Required | Notes |
| --- | --- | --- | --- |
| `spec` | [ReportSpecInput](/types/ReportSpecInput/) | no | No further notes. |
| `reportId` | [ID](/types/#scalars) | no | No further notes. |

## Returns

[ReportRows](/types/ReportRows/) `ReportRows!` — A finished synchronous report run: the report in words, the terminal family, THE OUTPUT PLAN, the rows as ONE JSON array text (`rowsJson` — the planJson carrier idiom: a row's shape is the column plan's, so the receiver parses; money renders as a decimal string by the currency's exponent, a missing group key reads `(none)`), rowCount · groupCount (0 for a projection), the records examined (the walks plus every caption hydrated), the pre-read estimate, the elapsed ms, and the reference families whose `.caption` column was OMITTED because the caller may not list them.

## Example request

```graphql
query ExampleReportRun($spec: ReportSpecInput) {
  reportRun(spec: $spec) {
    words
    terminalFamily
    rowsJson
    rowCount
    groupCount
    examined
    elapsedMs
    captionsOmitted
  }
}
```

Variables:

```json
{
  "spec": {
    "plan": {
      "start": {
        "family": "<family>",
        "filter": {
          "clauses": [
            {
              "field": "<field>",
              "op": "<op>",
              "values": [
                "<values>"
              ]
            }
          ]
        }
      },
      "hops": [
        {
          "direction": "outbound",
          "field": "<field>",
          "family": "<family>",
          "filter": {
            "clauses": [
              {
                "field": "<field>",
                "op": "<op>",
                "values": [
                  "<values>"
                ]
              }
            ]
          }
        }
      ]
    },
    "columns": [
      "<columns>"
    ]
  }
}
```

## Example response

```json
{
  "data": {
    "reportRun": {
      "words": "<words>",
      "terminalFamily": "<terminal family>",
      "rowsJson": "<rows json>",
      "rowCount": 1,
      "groupCount": 1,
      "examined": 1,
      "elapsedMs": 1,
      "captionsOmitted": [
        "<captions omitted>"
      ]
    }
  },
  "extensions": {
    "at": {
      "callId": "01EXAMPLE-CALL-ID",
      "version": {
        "requested": null,
        "serviced": {
          "name": "genesis",
          "number": 0
        }
      }
    }
  }
}
```

## Errors this call can answer

- `VALIDATION/INVALID` — Something in the request is not valid. ([VALIDATION](/errors/VALIDATION/))
- `AUTHN/REQUIRED` — Sign in to do this. ([AUTHN](/errors/AUTHN/))
- `AUTHZ/FORBIDDEN` — Your role does not allow this action. ([AUTHZ](/errors/AUTHZ/))
- `RATE_LIMIT/THROTTLED` — Too many requests in a short time. ([RATE_LIMIT](/errors/RATE_LIMIT/))
- `NOT_FOUND/*` — That record could not be found. ([NOT_FOUND](/errors/NOT_FOUND/))
