---
title: "get sales new overview"
method: GET
path: "/api/v1/sales/new-overview"
tags: ["sales"]
---

# get sales new overview

`GET /api/v1/sales/new-overview`

## Request

- Base URL: `https://api.app.tightly.io`
- URL: `https://api.app.tightly.io/api/v1/sales/new-overview`
- Auth: HTTP bearer

The sales overview a dashboard is built from: `cards` carrying a value, the previous period's value and the change per measure, and `charts` carrying `revenue` (the measured series), `historical` (the requested range), `forecast` (the same length forward) and `last_year` (the same range shifted back 365 days).

All three of historical, forecast and last_year are the same length as the range requested: historical is the window `filter_args` sets, forecast starts the day after it ends, and last_year is that window a year earlier. Forecast points are predictions rather than measurements, and are answered on the same shape as the rest.

`period` sets the bucket, and `cards`, `charts` and `product_option` narrow what is computed, which is worth doing: this read is the most expensive one on Sales.

A key issued to one account is refused with `account_mismatch`: this read covers every account on the book. That account's own sales are at `GET /sales/table`.

Scope: `sales:read`.

## Query parameters

- `filter_args` string
- `period` 'daily' | 'weekly'
- `cards` string
- `charts` string
- `product_option` string

## Headers

- `Tightly-Version` string

## Response `200`

The sales overview

- GetSalesNewOverviewPayload
  - `cards` CardItem[]: Key metrics cards (from existing API)
    - `annualisable` boolean: True when this metric's period figure can meaningfully be projected to a year, currently `gmroi` only. False for flows (`total_revenue`, `sold_quantity`, `sale_order_count`), whose period total IS the answer, and for ratios that are already window-independent (`sell_through_rate`, `return_rate`, `avg_sell_price`). Read this rather than hardcoding which keys annualise.
    - `annualised_value` number, nullable: `value * 365 / window_days`, the figure to PRESENT for an annualisable metric, and the only one comparable to an industry benchmark, because the industry quotes GMROI annually and every benchmark we grade against is annual. A period GMROI of 1.56 over 91 days is an annualised 6.26: the same data reads as "barely above the minimum" or "above elite" depending only on the basis. When you report this number, say it is annualised and say over what window. IMPORTANT. Null in two distinct cases, and `annualisable` tells them apart: (a) `annualisable: false`, the metric is not one we project; (b) `annualisable: true` with `annualised_value: null`, the metric should be annualised but `window_days` is under 28, the minimum window (`MIN_ANNUALISABLE_WINDOW_DAYS`). Below that the multiplier amplifies noise as much as signal, a measured tenant's 3-day GMROI of 0.01 annualises to ~1.2, which looks like a real near-target answer and is nothing. In case (b) report `value` with its window and say it is too short to annualise or grade. Do not perform the multiplication yourself.
    - `change` number, nullable: Percentage change from previous period. IMPORTANT. Field semantics: `value` is the actual result for the date range passed in filter_args. `previous_value` is the implicit comparison period (same calendar range one year prior). `change` is the authoritative percentage change. Always read it directly, never compute a percentage yourself from value and previous_value. Always present `value` for the period you queried; never present `previous_value` as if it were the result for a period you explicitly requested.
    - `info` string: Human-readable metric name
    - `key` string: Metric identifier key
    - `previous_value` number, nullable: Previous period value for comparison
    - `unit` string: Unit of measurement (count, usd, percentage, ratio)
    - `value` number: Current period value
    - `window_days` integer, nullable: Length of that window in days, inclusive. IMPORTANT. `value` on every card on this endpoint is a period figure and is not annualised. This matters most for `gmroi`, which is gross margin over the window divided by average inventory at cost over the same window, so it scales with window length instead of describing a rate: the same tenant measured 0.18 over 7 days, 0.59 over 30 and 1.56 over 91. Always state the window when reporting `gmroi`, and never compare `value` to an annual GMROI benchmark (industry min 1.5 / target 3.0 / elite 4.5). Use `annualised_value` for that.
    - `window_end` string, nullable: Last day of the window `value` was measured over (inclusive).
    - `window_start` string, nullable: First day of the window `value` was measured over (inclusive).
  - `charts` object: All charts including existing (revenue, sales_by_variant) and new time-based charts
    - `forecast` SalesChartDataPoint[]: Forecasted sales data (90 days future from sales_velocity) - simplified 3 fields
      - `date` string, date: Date in ISO format (YYYY-MM-DD)
      - `sold_quantity` number, float: Number of units sold
      - `total_revenue` number, float: Total revenue (net sales after discounts and returns)
    - `historical` SalesChartDataPoint[]: Historical sales data (90 days past from sale_orders) - simplified 3 fields
      - `date` string, date: Date in ISO format (YYYY-MM-DD)
      - `sold_quantity` number, float: Number of units sold
      - `total_revenue` number, float: Total revenue (net sales after discounts and returns)
    - `last_year` SalesChartDataPoint[]: Last year comparison (180 day window) - simplified 3 fields
      - `date` string, date: Date in ISO format (YYYY-MM-DD)
      - `sold_quantity` number, float: Number of units sold
      - `total_revenue` number, float: Total revenue (net sales after discounts and returns)
    - `revenue` object[]: Existing revenue chart with all metrics (9 fields)
    - `sales_by_variant` object[]: Existing sales by variant breakdown

## Other responses

- `400`: Bad Request - Invalid request parameters or validation errors
- `401`: No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
- `403`: The key may not reach this operation. `scope_missing` when the key does not hold Sales; `organization_mismatch` when the path names an organisation that is not the key's; `ip_not_allowed` when the caller's address is outside the key's allowlist; `account_mismatch` when the key is issued to one account, because this operation covers every account.
- `429`: The key has spent its allowance for this minute. 600 reads and 120 writes a minute, counted per key, refused calls included. `Retry-After` says how long to wait.
- `500`: Internal Server Error - Database connection or processing errors
- `503`: The database is briefly out of reach, which is what a failover or a full connection pool looks like from outside. Wait the seconds `Retry-After` names and send the request again. A read is safe to repeat. A write is not, because the connection was lost in the middle of it, so read the object back before sending it a second time. Branch on the status rather than on `code`, which is `POSTGRES_UNAVAILABLE` from one of the two seams that raise this and `SERVICE_UNAVAILABLE` from the other.

---

[API](https://skmtc.dev/tightly/apis/tightly-api.md) · [All operations](https://skmtc.dev/tightly/apis/tightly-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tightly/apis/tightly-api/revisions/199f7eb35afb?raw)
