---
title: "get sales velocity table totals"
method: GET
path: "/api/v1/sales/velocity/table/totals"
tags: ["sales_velocity"]
---

# get sales velocity table totals

`GET /api/v1/sales/velocity/table/totals`

## Request

- Base URL: `https://api.app.tightly.io`
- URL: `https://api.app.tightly.io/api/v1/sales/velocity/table/totals`
- Auth: HTTP bearer

The sales velocity table's totals over every filtered row, as one entry per period: units sold and revenue, forecast demand and its revenue, effective demand (forecast adjusted for stockouts), the same period a year earlier, and the velocity rate the forecast used.

It takes the same `filter_args` and `search` as get_sales_velocity_table, so the totals match the rows a caller is looking at. `start_date` and `end_date` set the window, `include_last_year` adds the prior-year figures and `aggregation_mode` sets how periods are summed.

`first_forecast_date` is set only on the period holding the day forecasting first ran for this organisation, and is null on every other. Missing forecasts remain null rather than zero. Historical partial forecasts retain their recorded amount and expose `forecast_coverage` on commitment-scoped PostgreSQL reads.

Scope: `sales:read`.

## Query parameters

- `filter_args` string
- `search` string
- `start_date` string, date
- `end_date` string, date
- `include_last_year` boolean
- `aggregation_mode` 'daily' | 'weekly' | 'monthly'

## Headers

- `Tightly-Version` string

## Response `200`

The sales velocity totals

- object
  - `data` object: One entry per time bucket in `totals`, the `data_source` that served them (`ch` or `pg`), and the `scope` the denominators were counted over, or the reason there is none, which is not the same as a scope of nothing. On a weekly chart each bucket also names its week's key in `week_grids`, which converts that Monday-keyed week to the plan's 4-5-4 Sunday week: the plan week it falls inside, that week's fiscal year and number, and how the seven days split between it and the next (6 and 1). That split describes the keyed week itself, not the bucket: a bucket clipped by `start_date` or `end_date` covers only part of its week, and `min_date` / `max_date` remain the bucket's own bounds. Daily and monthly buckets carry `week_grid: null` and `week_grids` is empty, those buckets are not weeks.
  - `message` object: severity, service and desc.

## Other responses

- `400`: Bad Request - invalid parameters
- `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.
- `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.
- `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)
