# Reports

Discover and run governed sales, purchasing, and open-order rollups.

Canonical HTML: https://developer.shelfcycle.com/guides/reports

Discover, compare, and rank read-only commercial report rollups with key and user access checked at request time.

Reports are for aggregate sales, purchasing, and open-order views. Use them when a workflow needs a rollup, dashboard, or comparison table, not individual record detail.

## Endpoints

| Endpoint | Use it for |
| --- | --- |
| `GET /reports` | List report keys, parameters, dimensions, measures, required scope, and whether this key can run each report. |
| `GET /reports/{reportKey}` | Run one allowed report with documented parameters and offset pagination. |

Report routes require `reports:read` plus the acting user's live ShelfCycle permissions. `GET /me` also reports `effectiveCapabilities.reports.available` so clients can check available report keys before showing report actions.

## Discover reports

Call the catalog before running a report. The catalog is the best source for labels, accepted parameters, dimensions, measures, and per-key availability.

```bash
curl "$SHELFCYCLE_API_BASE_URL/reports" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "X-ShelfCycle-Client: Revenue dashboard"
```

```json
{
  "data": {
    "reports": [
      {
        "key": "sales-summary",
        "title": "Sales summary",
        "description": "Sales rollups by customer, product, salesperson, supplier, or month.",
        "params": [
          { "name": "groupBy", "required": true, "description": "One or two dimensions." },
          { "name": "startDate", "required": true, "description": "YYYY-MM-DD in the org timezone." },
          { "name": "endDate", "required": true, "description": "YYYY-MM-DD in the org timezone." },
          { "name": "comparisonStartDate", "required": false, "description": "Start of an optional comparison window." },
          { "name": "comparisonEndDate", "required": false, "description": "End of an optional comparison window." }
        ],
        "dimensions": ["customer", "productFamily", "productCode", "salesPerson", "supplier", "month"],
        "measures": ["salesCents", "productCostCents", "orderCostsCents", "totalCostCents", "gpCents", "gpPct"],
        "requiredScope": "reports:read",
        "allowedForThisKey": true
      }
    ]
  }
}
```

If `allowedForThisKey` is false, do not run that report with the same key.

## Run a report

```bash
curl "$SHELFCYCLE_API_BASE_URL/reports/sales-summary?groupBy=customer&startDate=2026-04-01&endDate=2026-06-30&comparisonStartDate=2026-01-01&comparisonEndDate=2026-03-31&dateField=invoiceDate&sortBy=salesDelta&sortDirection=desc&limit=50" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "X-ShelfCycle-Client: Revenue dashboard"
```

Report responses include report metadata, normalized params, basis notes, rows, complete filtered-population totals, and pagination metadata. Rows always include `groupId`, `bucketKey`, and `groupName`; two-level groupings also include `group2Id`, `group2Key`, and `group2Name`.

Money values are cents-as-string in the org base currency. Keep them as integer strings or decimal-safe values in your application. Do not convert cents to JavaScript floating dollars for comparisons.

## Compare two periods

Add `comparisonStartDate` and `comparisonEndDate` together to compare the primary window with another bounded period. The windows can be adjacent, overlapping, equal in length, or different in length. Each window can span up to 366 days, with up to 732 combined days.

When comparison dates are present, sales and purchase rows include:

- `comparison` measures for the comparison window
- `deltas` calculated as primary minus comparison
- `comparisonClassification` with `new`, `lost`, or `retained` activity

Use `comparisonClassification` to focus on groups active only in the primary period, only in the comparison period, or in both. Classification follows recorded activity, so zero-dollar and credit activity are not mistaken for inactivity.

Sorting happens across the complete filtered population before `limit` and `offset` are applied. Sales summaries can sort by sales, gross profit, comparison values, or their changes. Purchase summaries can sort by spend, comparison spend, or spend change. Use `sortDirection=desc` for the largest increases and `sortDirection=asc` for the largest declines.

`data.totals` covers the filtered population before pagination. `meta.rankingComplete` confirms that ranking considered that population, while `meta.rowsCoverFullPopulation` tells you whether the returned rows contain the complete filtered result. This lets a workflow make a top-ranked claim without treating one page as a complete list.

## Available reports

| Report key | What it returns |
| --- | --- |
| `sales-summary` | Sales, cost, gross profit, and gross profit percent by one or two dimensions: customer, product family, product code, salesperson, supplier, or month. |
| `purchase-summary` | Material purchase-order spend by supplier, product code, or product family. |
| `open-sales-orders` | Current open sales-order value by status or customer. |

## Parameter notes

`sales-summary` accepts `startDate` and `endDate` in `YYYY-MM-DD` format and `dateField` as `invoiceDate`, `shipDate`, or `orderDate`. It supports one or two grouping dimensions. For period comparisons, use customer, product family, product code, salesperson, or supplier dimensions. Month remains available for single-period rollups.

`purchase-summary` uses purchase-order created dates and supports supplier, product-code, or product-family comparisons. Its spend is material purchase-order item amount only. It excludes landed costs, standalone purchase-order cost rows, AP cash payments, quantity, and unit-cost measures.

`open-sales-orders` uses current sales orders in confirmed and shipped states. `includeDraft=true` adds draft orders. It returns open sell value only, without cost or margin.

Reports paginate with `limit` and `offset`. Finder and reference-directory routes use their own documented pagination.

> **Guardrail**: Reports are read-only rollups. Use detail endpoints for selected records, not reports, when a workflow needs record-specific fields, notes, documents, or write readiness.
