# Reporting / Special Reports

# Special Reports

Some reports in Shopfront can't be written with the [Reporting Query Language](/documentation/Reporting/Syntax), these
are available as their own endpoints instead. Each one runs for a single Outlet and responds in the same shape as a
[successful query](/documentation/Reporting/Introduction#successfulQuery), always as a single page.

Unlike the reports in Shopfront, values aren't formatted for display: numbers are sent as numbers, dates are sent as
`YYYY-MM-DD` in the Vendor's timezone (or `null` when there isn't one), and each product row has an `ID` column with the
product's ID alongside its `Name`.

### Scopes
The special reports require the same scopes as the rest of the Reporting API:

* `run_advanced_reports`

For further information on scopes, please see [Scopes](/documentation/General/Scopes).

### Rate Limit
The special reports share the Reporting API's rate limit, each request uses one request from the same limit as
querying `/api/v2/reports`. The `X-Reporting-RateLimit-Remaining` header contains the number of requests you have
remaining, once it reaches zero you'll receive a status of `429` until the limit has reset.

### Errors
If the Outlet can't be found, the application doesn't have access to report on it, or a parameter is missing or
invalid, you'll receive a status of `400` with an `error` message. If the application is missing the required scopes, you'll
receive a status of `403` in the same format as a [failed query](/documentation/Reporting/Introduction#failedQuery).

**Error Response (JSON)**

```json
{
    "error": "The user does not have access to this Outlet for reporting"
}
```

## Slow Moving Stock
Lists the Outlet's in stock products that are selling slowly, ordered by their score (slowest first). Products that have
never sold are scored by the number of days since they were created multiplied by their inventory, otherwise they're
scored by how many days of their average daily sales the current inventory would cover.

Send a `POST` request to `https://[vendor].onshopfront.com/api/v2/reports/slow-moving-stock` with the following
parameters:

| Parameter | Required | Description |
| --- | --- | --- |
| outlet | Required | The ID of the Outlet to report on |
| brands | Optional | Only include products in these Brand IDs |
| categories | Optional | Only include products in these Category IDs |
| families | Optional | Only include products in these Family IDs |
| tags | Optional | Only include products with these Tag IDs |

Each row contains the columns `ID`, `Name`, `Average Sold Per Day`, `Inventory`, `Creation Date`, `Last Sold`,
`Last Ordered`, `Last Received`, and `Score`.

**Slow Moving Stock (HTTP)**

```http
POST /api/v2/reports/slow-moving-stock HTTP/1.1
Host: example.onshopfront.com
Accept: application/json
Authorization: Bearer ...
Content-Type: application/json
{
    "outlet": "11e6d61b3dec425286c45cf9dd713698",
    "categories": [
        "11e7a2c4f0d84c6ab2d1e2c5a1f3b4d5"
    ]
}
```

**Success Response (JSON)**

```json
{
    "completed": true,
    "data": {
        "columns": [
            "ID",
            "Name",
            "Average Sold Per Day",
            "Inventory",
            "Creation Date",
            "Last Sold",
            "Last Ordered",
            "Last Received",
            "Score"
        ],
        "data": [
            {
                "ID": "11e8b1c2d3e44f5a9b6c7d8e9f0a1b2c",
                "Name": "Pale Ale 6 Pack",
                "Average Sold Per Day": 0,
                "Inventory": 24,
                "Creation Date": "2026-03-02",
                "Last Sold": null,
                "Last Ordered": "2026-03-02",
                "Last Received": "2026-03-04",
                "Score": 5376
            },
            {
                "ID": "11e8b1c2d3e44f5a9b6c7d8e9f0a1b2d",
                "Name": "Shiraz 750ml",
                "Average Sold Per Day": 0.042,
                "Inventory": 18,
                "Creation Date": "2025-11-20",
                "Last Sold": "2026-08-14",
                "Last Ordered": "2025-11-20",
                "Last Received": "2025-11-22",
                "Score": 429
            }
        ],
        "groups": [],
        "totals": []
    }
}
```

## Last Stocktaked
Lists when each of the Outlet's products was last stocktaked, on or before the end of a date, and who stocktaked it.

Send a `POST` request to `https://[vendor].onshopfront.com/api/v2/reports/last-stocktaked` with the following
parameters:

| Parameter | Required | Description |
| --- | --- | --- |
| outlet | Required | The ID of the Outlet to report on |
| date | Required | The date (`YYYY-MM-DD`) to report up to the end of |
| showZeroInventory | Optional | Whether products with no inventory are included (defaults to false) |
| includeInactive | Optional | Whether inactive products are included, adding a `Status` column (defaults to false) |
| brands | Optional | Only include products in these Brand IDs |
| categories | Optional | Only include products in these Category IDs |
| families | Optional | Only include products in these Family IDs |
| tags | Optional | Only include products with these Tag IDs |

Each row contains the columns `ID`, `Name`, `Category`, `Date`, `Inventory`, and `User` (the name of who performed
the stocktake).

**Last Stocktaked (HTTP)**

```http
POST /api/v2/reports/last-stocktaked HTTP/1.1
Host: example.onshopfront.com
Accept: application/json
Authorization: Bearer ...
Content-Type: application/json
{
    "outlet": "11e6d61b3dec425286c45cf9dd713698",
    "date": "2026-10-16",
    "showZeroInventory": false,
    "includeInactive": false
}
```

**Success Response (JSON)**

```json
{
    "completed": true,
    "data": {
        "columns": [
            "ID",
            "Name",
            "Category",
            "Date",
            "Inventory",
            "User"
        ],
        "data": [
            {
                "ID": "11e8b1c2d3e44f5a9b6c7d8e9f0a1b2c",
                "Name": "Pale Ale 6 Pack",
                "Category": "Beer",
                "Date": "2026-09-30",
                "Inventory": 24,
                "User": "Sam Smith"
            }
        ],
        "groups": [],
        "totals": []
    }
}
```

## Inventory At Date
Calculates the inventory levels and stock value of the Outlet's products at a previous date.

Send a `POST` request to `https://[vendor].onshopfront.com/api/v2/reports/inventory-at-date` with the following
parameters:

| Parameter | Required | Description |
| --- | --- | --- |
| outlet | Required | The ID of the Outlet to report on |
| date | Required | The date and time to report the inventory at, in the Vendor's timezone |
| showZero | Optional | Whether products with no inventory are included (defaults to true) |
| groupBy | Optional | Group the products by `category`, `brand`, `family`, `tag`, `core_supplier`, or `last_supplier` |
| showProducts | Optional | When grouping, whether each product is included or only the group totals (defaults to true) |
| columns | Optional | The column keys to include, see `data.columns` in the response for the available keys |
| sort | Optional | The column key to sort the products by (defaults to `Name`) |
| direction | Optional | Either `asc` or `desc` (defaults to `asc`) |

The column names for quantities and costs use the Vendor's quantity terminology (e.g. "Case Quantity"), so
`data.columns` is an object of each column's key to its name. When products are grouped, each group also contains the
`id` of the classification it's grouped by, and when `showProducts` is false each row's `ID` is the classification's
ID instead of a product's (`null` for the group of products without one).

**Inventory At Date (HTTP)**

```http
POST /api/v2/reports/inventory-at-date HTTP/1.1
Host: example.onshopfront.com
Accept: application/json
Authorization: Bearer ...
Content-Type: application/json
{
    "outlet": "11e6d61b3dec425286c45cf9dd713698",
    "date": "2026-06-30 23:59:59",
    "showZero": false
}
```

**Success Response (JSON)**

```json
{
    "completed": true,
    "data": {
        "columns": {
            "ID": "ID",
            "Name": "Name",
            "Outlet": "Outlet",
            "case_quantity_label": "Case Quantity",
            "case_level_label": "Case Level",
            "single_level_label": "Single Level",
            "total_single_label": "Total Singles",
            "last_case_cost_ex_label": "Last Case Cost (Ex)",
            "last_case_cost_label": "Last Case Cost",
            "Stock Value (Ex) (Last)": "Stock Value (Ex) (Last)",
            "Stock Value (Inc) (Last)": "Stock Value (Inc) (Last)"
        },
        "data": [
            {
                "ID": "11e8b1c2d3e44f5a9b6c7d8e9f0a1b2c",
                "Name": "Pale Ale 6 Pack",
                "Outlet": "Main Store",
                "Case Quantity": 4,
                "Case Level": 6,
                "Single Level": 0,
                "Total Singles": 24,
                "Last Case Cost (Ex)": 12.5,
                "Last Case Cost": 13.75,
                "Stock Value (Ex) (Last)": 75,
                "Stock Value (Inc) (Last)": 82.5
            }
        ],
        "groups": [],
        "totals": {
            "Stock Value (Ex) (Last)": 75,
            "Stock Value (Inc) (Last)": 82.5
        }
    }
}
```