Skip to content

Stock ​

5 reads, no writes, on train 2026-11. Scopes: inventory:read.

OperationMethodScopePath
Get allocation matrixGETinventory:read/api/v1/inventory/allocation-matrix
Get forecast accuracyGETinventory:read/api/v1/inventory/forecast-accuracy
One product's position at a warehouseGETinventory:read/api/v1/inventory/position
Stock on hand at each period boundaryGETinventory:read/api/v1/inventory/stock-on-hand
Every variant with its stockGETinventory: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.

RequiredInWhat it is
variant_idsqueryComma-separated list of variant IDs to include in the matrix (max 50, min 1). Always required.
OptionalInWhat it is
Tightly-VersionheaderThe 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_missing when the key does not hold Stock; 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.
  • 404 Data not found
  • 429

Try it in the reference

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.

OptionalInWhat it is
weeksqueryWindow and cap, the most recent N measured whole weeks.
start_datequeryNarrow to measured weeks starting on or after this date.
end_datequeryNarrow to measured weeks ending on or before this date.
current_statequeryOpt 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_typequeryRestrict every figure to one category from products.product_type, matched exactly against the stored slice.
sales_channel_idqueryRestrict every figure to one sales channel, wholesale and retailer channels included.
Tightly-VersionheaderThe 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_missing when the key does not hold Stock; 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

Try it in the reference

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.

RequiredInWhat it is
variant_idqueryThe product variant to read the position of.
OptionalInWhat it is
location_idqueryThe warehouse.
Tightly-VersionheaderThe 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_id was 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_missing when the key does not hold Stock; 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.
  • 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

Try it in the reference

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.

RequiredInWhat it is
start_datequeryFirst day of the window (YYYY-MM-DD).
end_datequeryLast day of the window, inclusive (YYYY-MM-DD). At most 400 days after the start.
OptionalInWhat it is
grainqueryThe period the ladder is cut into. The same two keys the net sales feed serves.
categoriesqueryComma-separated categories to filter to. Filters; never changes the grain.
locationsqueryComma-separated location ids to filter to.
Tightly-VersionheaderThe 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_date precedes start_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_missing when the key does not hold Stock; 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

Try it in the reference

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.

OptionalInWhat it is
include_stock_availabilityqueryReturn stock_availability per row: isp and wisp today; isp_30d and wisp_30d over the last 30 completed days of the organisation's calendar.
offsetqueryThe number of rows to skip before starting to return items.
limitqueryThe maximum number of rows to return.
typequeryView type - 'variants' shows individual variants, 'products' shows grouped by product.
filter_argsqueryA JSON array of {key, operation, value}. The allowlist of keys and operations, and what each one means, is on the schema below.
sort_argsqueryComma-separated columns to sort by; prefix a column with - for descending.
searchquerySearch term for filtering results.
distinctqueryComma-separated distinct columns (e.g. variant_id,location_id).
fieldsqueryComma-separated list of fields to return in each row.
viewqueryWhen set to 'combined', returns a product-level view without requiring type=products.
bom_idqueryBill of materials ID to filter by.
exportqueryWhen true, returns an export URL instead of table data.
Tightly-VersionheaderThe 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_missing when the key does not hold Stock; 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

Try it in the reference

Tightly API, version 2026-11.