Appearance
Stock
5 reads, no writes, on train 2026-11. Scopes: inventory:read.
| Operation | Method | Scope | Path |
|---|---|---|---|
| Get allocation matrix | GET | inventory:read | /api/v1/inventory/allocation-matrix |
| Get forecast accuracy | GET | inventory:read | /api/v1/inventory/forecast-accuracy |
| One product's position at a warehouse | GET | inventory:read | /api/v1/inventory/position |
| Stock on hand at each period boundary | GET | inventory:read | /api/v1/inventory/stock-on-hand |
| Every variant with its stock | GET | inventory:read | /api/v1/inventory/table |
Get allocation matrix
GET /api/v1/inventory/allocation-matrix
Scopes: inventory:read
Stock spread across warehouses for the variants requested: the locations as columns, one row per variant, and a cell per pair carrying on-hand quantity, days and weeks of cover and a health word. Built for finding imbalances between sites.
| Required | In | What it is |
|---|---|---|
variant_ids | query | Comma-separated list of variant IDs to include in the matrix (max 50, min 1). Always required. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/inventory/allocation-matrix?variant_ids=<variant_ids>" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"locations": [
{
"location_id": "loc_0004",
"location_name": "London DC"
},
{
"location_id": "loc_0009",
"location_name": "Rotterdam DC"
}
],
"variants": [
{
"cells": [
{
"days_of_cover": 18,
"health": "caution",
"location_id": "loc_0004",
"on_hand": 302,
"weeks_of_cover": 2.6
},
{
"days_of_cover": 0,
"health": "critical",
"location_id": "loc_0009",
"on_hand": 0,
"weeks_of_cover": 0
}
],
"product_id": "4410092",
"sku": "TB-CREW-BLK-M",
"variant_id": "44100920011",
"variant_title": "Black / M"
}
]
},
"message": {
"desc": "",
"service": "inventory"
…
}
}What it refuses
- 400 Bad Request - missing or invalid variant_ids
- 401
key_invalid: "The API key is not valid." 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
scope_missing: "This key cannot read Stock." The key may not reach this operation.scope_missingwhen the key does not hold Stock;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404 Data not found
- 429
Get forecast accuracy
GET /api/v1/inventory/forecast-accuracy
Scopes: inventory:read
What the weekly forecast-accuracy job measured, read back: a trailing window and a latest week, each carrying wmape, wmase and the population they were scored over, and a weeks series behind them. Nothing is recomputed, so the figures are the ones measured at the time.
| Optional | In | What it is |
|---|---|---|
weeks | query | Window and cap, the most recent N measured whole weeks. |
start_date | query | Narrow to measured weeks starting on or after this date. |
end_date | query | Narrow to measured weeks ending on or before this date. |
current_state | query | Opt out of the two read-time inventories, forecast coverage and model provenance, which are computed against the live catalogue on every request and are most of this endpoint's cost. |
product_type | query | Restrict every figure to one category from products.product_type, matched exactly against the stored slice. |
sales_channel_id | query | Restrict every figure to one sales channel, wholesale and retailer channels included. |
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/inventory/forecast-accuracy" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"latest": {
"total_actual": 18400,
"total_predicted": 17920,
"total_variants": 4120,
"variants_with_sales": 3880,
"week_end_date": "2026-08-30",
"week_start_date": "2026-08-24",
"wmape": 0.261,
"wmase": 0.78
},
"refusal": null,
"scope": null,
"trailing": {
"average_bias": -0.031,
"computed_at": "2026-09-01T06:31:00+00:00",
"false_demand_rate": 0.06,
"measured_days": 364,
"population": 4120,
"population_label": "variants with sales in the window",
"weeks": 52,
"weeks_missing": 0,
"window_end_date": "2026-08-31",
"window_start_date": "2025-09-08",
"wmape": 0.284,
"wmase": 0.81,
"wmase_baseline": "seasonal_naive"
},
"weeks": [
{
"week_start_date": "2026-08-17",
"wmape": 0.297,
"wmase": 0.84
},
{
"week_start_date": "2026-08-24",
"wmape": 0.261,
"wmase": 0.78
}
…
]
}
}What it refuses
- 400 Bad Request. An out-of-range
weeks, or an unparseable date - 401
key_invalid: "The API key is not valid." 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
scope_missing: "This key cannot read Stock." The key may not reach this operation.scope_missingwhen the key does not hold Stock;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 429
One product's position at a warehouse
GET /api/v1/inventory/position
Scopes: inventory:read
What is there, what is held, what is coming and what is left to sell, for one product at one warehouse: on_hand, reserved broken down by why each unit is spoken for and by which order holds it, expected_inbound broken down by the purchase or transfer order it is coming on, and atp.
| Required | In | What it is |
|---|---|---|
variant_id | query | The product variant to read the position of. |
| Optional | In | What it is |
|---|---|---|
location_id | query | The warehouse. |
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/inventory/position?variant_id=<variant_id>" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"as_of": "2026-09-05T06:05:12+00:00",
"atp": 566,
"expected_inbound": {
"by_document": [
{
"due": "2026-09-19",
"id": "1042",
"name": "PO-00001042",
"quantity": 200,
"supply_class": "firm",
"type": "purchase_order"
}
],
"quantity": 200,
"returns_expected": 0
},
"location_id": "loc-lb",
"location_name": "Long Beach",
"locations": [],
"movements": [
{
"actor": null,
"document": {
"id": "31",
"name": "RET-00000031",
"type": "return"
},
"id": 9013,
"kind": "return",
"landed_unit_cost": {
"cents": null,
"currency": null,
"reason": "Not measured",
"usd": null
},
"occurred_at": "2026-09-05T02:14:00+00:00",
"quantity_delta": 2,
"unit_cost": {
…
}
}
]
}
}What it refuses
- 400 No
variant_idwas sent, or a parameter could not be read. - 401
key_invalid: "The API key is not valid." 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. - 403
scope_missing: "This key cannot read Stock." The key may not reach this operation.scope_missingwhen the key does not hold Stock;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 404
variant_not_on_file: "No product with that id is on file." No product or no warehouse with that id is on file (variant_not_on_file,location_not_on_file). - 429
Stock on hand at each period boundary
GET /api/v1/inventory/stock-on-hand
Scopes: inventory:read
Stock at each period boundary, per category and location: one row per period with its opening and closing level, plus the window, the grain, the calendar it was bucketed on, a coverage block and a basis note.
| Required | In | What it is |
|---|---|---|
start_date | query | First day of the window (YYYY-MM-DD). |
end_date | query | Last day of the window, inclusive (YYYY-MM-DD). At most 400 days after the start. |
| Optional | In | What it is |
|---|---|---|
grain | query | The period the ladder is cut into. The same two keys the net sales feed serves. |
categories | query | Comma-separated categories to filter to. Filters; never changes the grain. |
locations | query | Comma-separated location ids to filter to. |
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/inventory/stock-on-hand?start_date=<start_date>&end_date=<end_date>" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"basis_note": "Levels at each period boundary, read from the nightly inventory archive.",
"calendar": "retail_454",
"channel_axis": null,
"cover_note": "Every location in scope reported on the boundary date.",
"coverage": {
"archive": "complete",
"carry_forward_days": 0,
"categories": 14,
"locations": 4,
"max_staleness_days": 1,
"pairs": 4120,
"pairs_costed": 3990,
"pairs_measured_at": "2026-06-30",
"pairs_priced": 4008,
"periods": 26,
"periods_with_closing": 26,
"periods_with_opening": 26,
"unattributed_reason": null,
"unattributed_units": 0,
"unattributed_value_at_cost": 0
},
"grain": "retail_week",
"rows": [
{
"category": "Knitwear",
"closing": 17120,
"opening": 18400,
"period_start": "2026-01-05"
},
{
"category": "Knitwear",
"closing": 16008,
"opening": 17120,
"period_start": "2026-01-12"
}
],
"window": {
"end_exclusive": "2026-07-01"
…
}
}
}What it refuses
- 400 A date could not be read,
end_dateprecedesstart_date, the window exceeds 400 days, or the grain is not one the calendar module serves. - 401
key_invalid: "The API key is not valid." 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
scope_missing: "This key cannot read Stock." The key may not reach this operation.scope_missingwhen the key does not hold Stock;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 429
Every variant with its stock
GET /api/v1/inventory/table
Scopes: inventory:read
A page of stock: on hand, incoming, committed and available quantities, days of cover and a health word, with filtered_max_size for the filtered set's size.
| Optional | In | What it is |
|---|---|---|
offset | query | The number of rows to skip before starting to return items. |
limit | query | The maximum number of rows to return. |
type | query | View type - 'variants' shows individual variants, 'products' shows grouped by product. |
filter_args | query | A JSON array of {key, operation, value}. The allowlist of keys and operations, and what each one means, is on the schema below. |
sort_args | query | Comma-separated columns to sort by; prefix a column with - for descending. |
search | query | Search term for filtering results. |
distinct | query | Comma-separated distinct columns (e.g. variant_id,location_id). |
fields | query | Comma-separated list of fields to return in each row. |
view | query | When set to 'combined', returns a product-level view without requiring type=products. |
bom_id | query | Bill of materials ID to filter by. |
export | query | When true, returns an export URL instead of table data. |
Tightly-Version | header | The date train to answer on. |
bash
curl "https://api.app.tightly.io/api/v1/inventory/table" \
-H "Authorization: Bearer $TIGHTLY_API_KEY"What it answers
json
{
"data": {
"filtered_max_size": 4120,
"filtered_max_unique_size": 1284,
"offset": 0,
"rows": [
{
"available_qty": 261,
"committed_qty": 41,
"days_of_cover": 18,
"health": "caution",
"incoming_qty": 800,
"location_id": "loc_0004",
"location_name": "London DC",
"on_hand_qty": 302,
"product_id": "4410092",
"product_title": "Terry Crew",
"sku": "TB-CREW-BLK-M",
"variant_id": "44100920011",
"variant_title": "Black / M"
}
],
"size": 1
},
"message": {
"desc": "",
"service": "inventory",
"severity": "INFO"
}
}What it refuses
- 401
key_invalid: "The API key is not valid." 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
scope_missing: "This key cannot read Stock." The key may not reach this operation.scope_missingwhen the key does not hold Stock;organization_mismatchwhen the path names an organisation that is not the key's;ip_not_allowedwhen the caller's address is outside the key's allowlist. - 429