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
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.