Skip to content

Products ​

19 reads, no writes, on train 2026-11. Scopes: products:read.

OperationMethodScopePath
Every value the product reads can be filtered byGETproducts:read/api/v1/organizations/{organization_id}/products/filters
What the catalogue is worth on handGETproducts:read/api/v1/organizations/{organization_id}/products/overview
The subcategories under one categoryGETproducts:read/api/v1/product-subcategories
One product with its stock and the ranges its variants spanGETproducts:read/api/v1/product/{product_id}
The values a product's own rows can be filtered byGETproducts:read/api/v1/product/{product_id}/filters
What is still on order for a productGETproducts:read/api/v1/product/{product_id}/incoming-pos
What is still on order for a productGETproducts:read/api/v1/product/{product_id}/incoming-pos/drawer
Every variant of one productGETproducts:read/api/v1/product/{product_id}/variants
What the scoped products are missing for planningGETproducts:read/api/v1/products/completeness
A page of the product catalogueGETproducts:read/api/v1/products/table
One variant with its stock and its suppliersGETproducts:read/api/v1/variants/{variant_id}
One variant's stock and coverGETproducts:read/api/v1/variants/{variant_id}/drawer-overview
One variant's own timeline of changesGETproducts:read/api/v1/variants/{variant_id}/events
What is still on order for one variantGETproducts:read/api/v1/variants/{variant_id}/incoming-pos
How this variant's sales have moved when its price movedGETproducts:read/api/v1/variants/{variant_id}/price-sensitivity
The custom fields this organisation keeps on its variantsGETproducts:read/api/v1/variants/custom-fields
One custom field definitionGETproducts:read/api/v1/variants/custom-fields/{custom_field_id}
What changed on the catalogueGETproducts:read/api/v1/variants/events
A page of variantsGETproducts:read/api/v1/variants/table

Every value the product reads can be filtered by ​

GET /api/v1/organizations/{organization_id}/products/filters

Scopes: products:read

The values that exist in this organisation's catalogue, so a filter can be built from what is there rather than guessed: category, supplier, vendor, product_status, tags, shopify_tags, class (the performance categories), location, missing_fields, metafields, custom_fields and country_of_origin (ISO 3166-1 alpha-2).

RequiredInWhat it is
organization_idpathThe organisation.
OptionalInWhat it is
fieldqueryReturn one paginated list instead of every filter.
searchqueryNarrow the paginated list. Ignored when field is absent.
offsetqueryValues to skip. Ignored when field is absent.
limitqueryValues to return. Ignored when field is absent.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/products/filters" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "category": [
      "Outerwear",
      "Knitwear"
    ],
    "class": [
      "Best sellers",
      "Slow movers"
    ],
    "country_of_origin": [
      "PT",
      "IT"
    ],
    "custom_fields": [
      "Fabric composition"
    ],
    "location": [
      {
        "id": "61240442",
        "name": "Rotterdam DC"
      }
    ],
    "metafields": [
      "custom.fabric"
    ],
    "missing_fields": [
      "hs_code"
    ],
    "product_status": [
      "ACTIVE",
      "ARCHIVED",
      "DRAFT",
      "UNLISTED"
    ],
    "shopify_tags": [
      "aw26"
    ],
    "supplier": [
      {
        …
      }
    ]
  }
}

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, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; organization_mismatch when the path names an organisation that is not the key's, which is refused rather than substituted; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 429

Try it in the reference

What the catalogue is worth on hand ​

GET /api/v1/organizations/{organization_id}/products/overview

Scopes: products:read

Three figures for the catalogue, each as a card: current_inventory_cost (stock on hand at cost), incoming_stock_cost (what is on order, at cost) and expected_revenue (the same stock at its selling price).

RequiredInWhat it is
organization_idpathThe organisation.
OptionalInWhat it is
filter_argsqueryA JSON array of {key, operation, value} narrowing the figures.
searchqueryFree text, applied to the same set the products table would match.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/organizations/<organization_id>/products/overview" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "cards": [
      {
        "change": 0.0717,
        "higher_better": true,
        "info": "Stock on hand valued at unit cost.",
        "key": "current_inventory_cost",
        "previous_value": 1198400,
        "unit": "USD",
        "value": 1284300
      },
      {
        "change": -0.2149,
        "higher_better": true,
        "info": "Open orders valued at unit cost.",
        "key": "incoming_stock_cost",
        "previous_value": 402100,
        "unit": "USD",
        "value": 315700
      },
      {
        "change": 0.0698,
        "higher_better": true,
        "info": "Stock on hand valued at its selling price.",
        "key": "expected_revenue",
        "previous_value": 3188200,
        "unit": "USD",
        "value": 3410800
      }
    ]
  },
  "message": {
    "desc": "OK",
    "service": "inventory",
    "severity": "INFO"
  }
}

What it refuses

  • 400 filter_args is not valid JSON, or names a key this read does not accept.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; organization_mismatch when the path names an organisation that is not the key's, which is refused rather than substituted; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 429

Try it in the reference

The subcategories under one category ​

GET /api/v1/product-subcategories

Scopes: products:read

The active subcategories belonging to one category, each {subcategory_id, name, category_id, is_active}. Read it to resolve a subcategory name to the id a product carries, or to offer the choices under a category.

RequiredInWhat it is
categoryqueryThe category name or category_id whose subcategories to retrieve.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/product-subcategories?category=<category>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": [
    {
      "category_id": "cat_outerwear",
      "is_active": true,
      "name": "Shirt jackets",
      "subcategory_id": "sub_shirt_jackets"
    },
    {
      "category_id": "cat_outerwear",
      "is_active": true,
      "name": "Parkas",
      "subcategory_id": "sub_parkas"
    }
  ],
  "message": {
    "desc": "The subcategories on file",
    "service": "inventory",
    "severity": "SUCCESS"
  }
}

What it refuses

  • 400 The category query parameter is missing.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 404 No category in this organisation carries that name or id.
  • 429

Try it in the reference

One product with its stock and the ranges its variants span ​

GET /api/v1/product/{product_id}

Scopes: products:read

One product as the catalogue holds it: title, description, vendor, images, category, subcategory and family, status and published date, country of origin, HS code, unit of measure, and the sales channels it is sold through.

RequiredInWhat it is
product_idpathThe product this reads.
OptionalInWhat it is
location_idqueryNarrow in_stock, incoming_stock and inventory_value to one warehouse.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/product/<product_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "category": "Outerwear",
    "category_id": "cat_outerwear",
    "country_of_origin": "PT",
    "currency": "USD",
    "description": "A brushed wool overshirt cut for layering.",
    "family": "Tops",
    "family_id": "fam_tops",
    "gallery_images": [
      "https://cdn.tightly.io/p/7412095483953.jpg"
    ],
    "hs_code": "620331",
    "image_url": "https://cdn.tightly.io/p/7412095483953.jpg",
    "in_current_season_plan": true,
    "in_stock": 1840,
    "incoming_stock": 600,
    "inventory_value": 71760,
    "is_managed": true,
    "is_seasonal": true,
    "last_updated_at": "2026-09-01T11:42:00+00:00",
    "last_updated_by": "ana.ruiz@example.com",
    "launch_date": "2026-02-14",
    "lead_time": {
      "max": 60,
      "min": 45
    },
    "min_order_quantity": {
      "max": 120,
      "min": 120
    },
    "num_variants": 8,
    "options": {
      "Colour": [
        "Charcoal",
        "Sand"
      ],
      "Size": [
        "S",
        "M"
        …
      ]
    }
  }
}

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, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 404 No product in this organisation carries that id.
  • 429

Try it in the reference

The values a product's own rows can be filtered by ​

GET /api/v1/product/{product_id}/filters

Scopes: products:read

The filter values that exist for one product: categories, the category names its variants carry, and locations, the warehouses it is stocked in. Read it to populate a picker before narrowing the product's own reads, rather than sending a value the catalogue does not hold and getting an empty page back.

RequiredInWhat it is
product_idpathThe product whose filter values this reads.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/product/<product_id>/filters" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "categories": [
      "Outerwear"
    ],
    "locations": [
      "Rotterdam DC",
      "New Jersey DC"
    ]
  },
  "message": {
    "desc": "OK",
    "service": "product",
    "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, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 404 No product in this organisation carries that id.
  • 429

Try it in the reference

What is still on order for a product ​

GET /api/v1/product/{product_id}/incoming-pos

Scopes: products:read

Open orders carrying any variant of one product, grouped under the variant rather than under the order: one entry per variant that has something inbound, each with po_count, to_count (transfer orders), proposal_count and the orders themselves.

RequiredInWhat it is
product_idpathThe product whose inbound orders this reads.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/product/<product_id>/incoming-pos" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "purchase_orders_grouped_by_variant": [
      {
        "po_count": 1,
        "proposal_count": 0,
        "purchase_orders": [
          {
            "delivered_quantity": 0,
            "deliveries": null,
            "expected_delivery_date": "2026-10-12",
            "has_delivery_delay": false,
            "id": "48120",
            "is_proposal": false,
            "line_items": [
              {
                "quantity": 240,
                "variant_id": "42318902722609",
                "variant_title": "Charcoal / S"
              }
            ],
            "location_id": "61240442",
            "location_name": "Rotterdam DC",
            "order_name": "PO-00048120",
            "order_type": "PURCHASE",
            "ordered_quantity": 240,
            "status": "FullyConfirmed",
            "supplier_id": "sup_1180",
            "supplier_name": "Atelier Norte",
            "supplier_signals": []
          }
        ],
        "to_count": 0,
        "total_delivered": 0,
        "total_ordered": 240,
        "variant_id": "42318902722609",
        "variant_name": "Charcoal / S"
      }
    ]
  }
  …
}

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, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 404 No product in this organisation carries that id.
  • 429

Try it in the reference

What is still on order for a product ​

GET /api/v1/product/{product_id}/incoming-pos/drawer

Scopes: products:read

The same open orders as the per-variant read, grouped the other way: by order_type, in the fixed order PURCHASE, TRANSFER, MANUFACTURING, with one entry per order rather than one per variant.

RequiredInWhat it is
product_idpathThe product whose inbound orders this reads.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/product/<product_id>/incoming-pos/drawer" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "purchase_orders_grouped_by_type": [
      {
        "order_type": "PURCHASE",
        "purchase_orders": [
          {
            "delivered_quantity": 0,
            "expected_delivery_date": "2026-10-12",
            "has_delivery_delay": false,
            "id": "48120",
            "line_items": [
              {
                "quantity": 240,
                "variant_id": "42318902722609",
                "variant_title": "Charcoal / S"
              },
              {
                "quantity": 360,
                "variant_id": "42318902755377",
                "variant_title": "Charcoal / M"
              }
            ],
            "name": "PO-00048120",
            "ordered_quantity": 600,
            "status": "FullyConfirmed",
            "supplier_signals": []
          }
        ]
      },
      {
        "order_type": "TRANSFER",
        "purchase_orders": []
      }
    ]
  },
  "message": {
    "desc": "OK",
    "service": "product",
    "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, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 404 No product in this organisation carries that id.
  • 429

Try it in the reference

Every variant of one product ​

GET /api/v1/product/{product_id}/variants

Scopes: products:read

The product's variants, three fields each: variant_id, variant_title and sku. It is the list to walk when you hold a product and need the variant ids underneath it.

RequiredInWhat it is
product_idpathThe product whose variants this lists.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/product/<product_id>/variants" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "variants": [
      {
        "sku": "AWO-CHR-S",
        "variant_id": "42318902722609",
        "variant_title": "Charcoal / S"
      },
      {
        "sku": "AWO-CHR-M",
        "variant_id": "42318902755377",
        "variant_title": "Charcoal / M"
      },
      {
        "sku": "AWO-SND-S",
        "variant_id": "42318902788145",
        "variant_title": "Sand / S"
      }
    ]
  },
  "message": {
    "desc": "OK",
    "service": "product",
    "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, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 404 No product in this organisation carries that id.
  • 429

Try it in the reference

What the scoped products are missing for planning ​

GET /api/v1/products/completeness

Scopes: products:read

A statement about a whole scope rather than a page of it: how many products it holds, how many are planning-complete, and one row per planning attribute saying how many products are missing it.

OptionalInWhat it is
searchqueryFree text over product title, SKU and vendor, as on the products table.
filter_argsqueryA JSON array of {key, operation, value}, the same keys the products table accepts.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/products/completeness" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "complete_products": 268,
    "fields": [
      {
        "affected_pairs": 0,
        "field": "category",
        "label": "Category",
        "missing_products": 0,
        "reason": "No category: analog matching and price banding fall back to the whole catalog.",
        "review_products": 0
      },
      {
        "affected_pairs": 742,
        "field": "price_band",
        "label": "Price band",
        "missing_products": 61,
        "reason": "No price band: suggestions can't band by architecture (good / better / best).",
        "review_products": 0
      },
      {
        "affected_pairs": 1109,
        "field": "size_curve",
        "label": "Size curve",
        "missing_products": 88,
        "reason": "No size curve: buys will use the bell default (1-2-2-1).",
        "review_products": 0
      },
      {
        "affected_pairs": 301,
        "field": "selling_window",
        "label": "Selling window",
        "missing_products": 24,
        "reason": "Seasonal without a selling window: replenishment can't hold to the season and plans can't phase.",
        "review_products": 0
      },
      {
        "affected_pairs": 144,
        "field": "cost",
        "label": "Unit cost"
        …
      }
    ]
  }
}

What it refuses

  • 400 filter_args is not valid JSON, or names a key the products table does not accept.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 429

Try it in the reference

A page of the product catalogue ​

GET /api/v1/products/table

Scopes: products:read

A page of products with the catalogue fields an integrator syncs on: title, vendor, category, num_variants, status, GTIN, description, currency, HS code, country of origin, unit of measure, sales_channels, and last_updated_at and last_updated_by.

OptionalInWhat it is
offsetqueryRows to skip before this page.
limitqueryRows in this page.
searchqueryFree text over product title, SKU and vendor.
filter_argsqueryA JSON array of {key, operation, value}.
sort_argsqueryComma-separated sort columns, - for descending and + or nothing for ascending.
exportqueryAnswer a download URL for the filtered set instead of a page of rows.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/products/table" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "filtered_max_size": 412,
    "max_size": 1806,
    "offset": 0,
    "products_count": 1,
    "rows": [
      {
        "category": "Outerwear",
        "category_id": "cat_outerwear",
        "country_of_origin": "PT",
        "currency": "USD",
        "description": "A brushed wool overshirt cut for layering.",
        "gallery_images": [
          "https://cdn.tightly.io/p/7412095483953.jpg"
        ],
        "gtin": "05012345678900",
        "height": null,
        "hs_code": "620331",
        "is_seasonal": true,
        "last_updated_at": "2026-09-01T11:42:00+00:00",
        "last_updated_by": "ana.ruiz@example.com",
        "length": null,
        "num_variants": 8,
        "planning_gaps": [],
        "price_band": "better",
        "price_band_source": "declared",
        "product_id": "7412095483953",
        "product_status": "ACTIVE",
        "product_title": "Alpine Wool Overshirt",
        "replenishment_mode": "seasonal",
        "sales_channels": [
          {
            "id": "61240442",
            "name": "Online Store"
          }
        ],
        "sell_price": {
          "max": 145,
          "min": 129
          …
        }
      }
    ]
  }
}

What it refuses

  • 400 offset or limit is outside its range, or filter_args is not valid JSON.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 429

Try it in the reference

One variant with its stock and its suppliers ​

GET /api/v1/variants/{variant_id}

Scopes: products:read

One variant as the catalogue holds it, and what is true of it right now: sku, barcode, gtin, lifecycle and its alias status, description and images, vendor, category, sell_price, unit_cost and unit_cost_currency, the physical fields, hs_code, country_of_origin, selected_options, custom_fields, launch_date, and the product_id and product_name it belongs to.

RequiredInWhat it is
variant_idpathThe variant this reads.
OptionalInWhat it is
location_idqueryNarrow in_stock, incoming_stock and inventory_value to one warehouse.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/variants/<variant_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "barcode": "5012345678900",
    "category": "Outerwear",
    "category_id": "cat_outerwear",
    "country_of_origin": "PT",
    "custom_fields": {
      "Fabric composition": "80% wool, 20% polyamide"
    },
    "description": "A brushed wool overshirt cut for layering.",
    "gtin": "05012345678900",
    "health_cover": [
      {
        "cover": 38,
        "health": "healthy",
        "location": "Rotterdam DC"
      }
    ],
    "hs_code": "620331",
    "image_url": "https://cdn.tightly.io/p/7412095483953.jpg",
    "in_stock": 214,
    "incoming_stock": 240,
    "inventory_value": 7276,
    "is_managed": true,
    "last_stockout_date": null,
    "launch_date": "2026-02-14",
    "lifecycle": "seasonal",
    "performance_category_by_location": [
      {
        "capital_quadrant": "Grow",
        "location": "Rotterdam DC",
        "performance_category": "Best sellers"
      }
    ],
    "product_id": "7412095483953",
    "product_name": "Alpine Wool Overshirt",
    "production_type": "buy_only",
    "published_status": "ACTIVE",
    "selected_options": {
      "Colour": "Charcoal"
      …
    }
  }
}

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, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 404 No variant in this organisation carries that id.
  • 429

Try it in the reference

One variant's stock and cover ​

GET /api/v1/variants/{variant_id}/drawer-overview

Scopes: products:read

One row per warehouse the variant is held in: location_id and location_name, stock_on_hand, stock_value, incoming_stock, weeks_of_cover, velocity (units a day), health and capital_quadrant.

RequiredInWhat it is
variant_idpathThe variant this reads.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/variants/<variant_id>/drawer-overview" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "is_seasonal": true,
    "locations": [
      {
        "capital_quadrant": "Grow",
        "health": "healthy",
        "incoming_stock": 240,
        "location_id": "61240442",
        "location_name": "Rotterdam DC",
        "stock_on_hand": 214,
        "stock_value": 7276,
        "velocity": 5.6,
        "weeks_of_cover": 5.4
      },
      {
        "capital_quadrant": null,
        "health": "critical",
        "incoming_stock": 0,
        "location_id": "61240443",
        "location_name": "New Jersey DC",
        "stock_on_hand": 0,
        "stock_value": 0,
        "velocity": null,
        "weeks_of_cover": null
      }
    ],
    "predecessors": [
      {
        "product_title": "Alpine Wool Overshirt (AW25)",
        "variant_id": "41180022331904",
        "variant_title": "Charcoal / S"
      }
    ],
    "successors": []
  },
  "message": {
    "desc": "OK",
    "service": "variant",
    "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, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 404 No variant in this organisation carries that id.
  • 429

Try it in the reference

One variant's own timeline of changes ​

GET /api/v1/variants/{variant_id}/events

Scopes: products:read

Every recorded change to one variant: id, event_type, event_date, previous_value, new_value, a typed metadata object, location_id, created_at and created_by_user_id. It carries no product or SKU fields, because they are the ones you name in the request.

RequiredInWhat it is
variant_idpathThe variant whose timeline this reads.
OptionalInWhat it is
event_typequeryComma-separated event types to filter by. Omit to return every type.
start_datequeryStart of the range (ISO 8601 date).
end_datequeryEnd of the range (ISO 8601 date). Defaults to today.
location_idqueryNarrow stock events to one warehouse, matched against metadata.location_changes.
limitqueryEvents in this page.
offsetqueryEvents to skip before this page.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/variants/<variant_id>/events" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "events": [
      {
        "created_at": "2026-08-19T09:14:00+00:00",
        "created_by_user_id": null,
        "event_date": "2026-08-19",
        "event_type": "price_change",
        "id": "evt_9f21c40b",
        "location_id": null,
        "metadata": {
          "currency": "USD"
        },
        "new_value": "129.00",
        "previous_value": "119.00"
      },
      {
        "created_at": "2026-07-02T16:38:00+00:00",
        "created_by_user_id": "usr_4471",
        "event_date": "2026-07-02",
        "event_type": "moq_change",
        "id": "evt_9e04a771",
        "location_id": null,
        "metadata": {
          "supplier_id": "sup_1180"
        },
        "new_value": "120",
        "previous_value": "100"
      }
    ],
    "filtered_count": 2,
    "limit": 8,
    "offset": 0,
    "total_count": 34
  },
  "message": {
    "desc": "SKU events retrieved",
    "service": "variant",
    "severity": "SUCCESS"
  }
  …
}

What it refuses

  • 400 A date could not be read, or offset or limit is outside its range.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 404 No variant in this organisation carries that id.
  • 429

Try it in the reference

What is still on order for one variant ​

GET /api/v1/variants/{variant_id}/incoming-pos

Scopes: products:read

The open orders carrying one variant, with the quantities counted for that variant alone.

RequiredInWhat it is
variant_idpathThe variant whose inbound orders this reads.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/variants/<variant_id>/incoming-pos" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "purchase_orders": [
      {
        "delivered_quantity": 0,
        "deliveries": null,
        "expected_delivery_date": "2026-10-12",
        "id": "48120",
        "line_items": [
          {
            "quantity": 240,
            "variant_id": "42318902722609",
            "variant_title": "Charcoal / S"
          }
        ],
        "location_id": "61240442",
        "location_name": "Rotterdam DC",
        "name": "PO-00048120",
        "ordered_quantity": 240,
        "status": "FullyConfirmed",
        "supplier_id": "sup_1180",
        "supplier_name": "Atelier Norte"
      }
    ]
  },
  "message": {
    "desc": "OK",
    "service": "variant",
    "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, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 404 No variant in this organisation carries that id.
  • 429

Try it in the reference

How this variant's sales have moved when its price moved ​

GET /api/v1/variants/{variant_id}/price-sensitivity

Scopes: products:read

What past price changes did to this variant's sales velocity, as a tier with the evidence behind it.

RequiredInWhat it is
variant_idpathThe variant this reads.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/variants/<variant_id>/price-sensitivity" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "basis": "measured",
    "confidence": "medium",
    "data_through": "2026-08-19",
    "evidence": "Seven qualifying price changes over 42 weeks.",
    "expected_error": 0.04,
    "n_events": 7,
    "periods_observed": 42,
    "price_variation": 0.11,
    "reference_basis": null,
    "reference_elasticity": null,
    "reference_source": null,
    "required_price_variation": 0.08,
    "shrunk_effect_down": 0.06,
    "shrunk_effect_up": -0.11,
    "sign_agreement": 0.86,
    "summary": "Sales dropped ~11% when price increased. Sales rose ~6% when price decreased.",
    "tier": "MODERATE",
    "worth_considering": "Price changes modestly affect sales (~11% velocity shift). Medium confidence. Last event analyzed: August 2026."
  },
  "message": {
    "desc": "Price sensitivity classification retrieved",
    "service": "variant",
    "severity": "SUCCESS"
  }
}

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, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 429

Try it in the reference

The custom fields this organisation keeps on its variants ​

GET /api/v1/variants/custom-fields

Scopes: products:read

A page of custom field definitions: what this organisation has added to its variants beyond the catalogue's own columns. Each carries id, name, field_type (string, decimal, integer, boolean or date), source, description, is_active, is_editable, is_value_editable, access_type, and its timestamps.

OptionalInWhat it is
offsetqueryDefinitions to skip before this page.
limitqueryDefinitions in this page.
searchqueryFree text over name and description.
filter_argsqueryA JSON array of {key, operation, value}.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/variants/custom-fields" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "custom_fields": [
      {
        "access_type": "read_write",
        "bound_to": null,
        "created_at": "2026-03-02T10:00:00+00:00",
        "description": "The mill's stated composition.",
        "field_type": "string",
        "grain": "product",
        "id": "123",
        "is_active": true,
        "is_editable": true,
        "is_value_editable": true,
        "label": "Composition",
        "name": "composition",
        "source": "tightly",
        "updated_at": "2026-08-14T08:21:00+00:00",
        "used_in": {
          "families": 2,
          "saved_filters": 0
        }
      },
      {
        "access_type": "read_only",
        "bound_to": {
          "connection_id": "66b1a0f4c2e91d0007a3b512",
          "field_key": "product.custom_field.fabric",
          "source_path": "metafields.custom.fabric"
        },
        "created_at": "2026-01-19T12:04:00+00:00",
        "description": "tightly:grain=product; label=Fabric",
        "field_type": "string",
        "grain": "product",
        "id": "141",
        "is_active": true,
        "is_editable": false,
        "is_value_editable": false,
        "label": "Fabric",
        "name": "fabric"
        …
      }
    ]
  }
}

What it refuses

  • 400 offset or limit is outside its range, or filter_args is not valid JSON.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 429

Try it in the reference

One custom field definition ​

GET /api/v1/variants/custom-fields/{custom_field_id}

Scopes: products:read

One custom field definition by its id: name, field_type, source, description, is_active, is_editable, is_value_editable, access_type and its timestamps.

RequiredInWhat it is
custom_field_idpathThe definition this reads, as a variant's custom_fields map keys it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/variants/custom-fields/<custom_field_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "custom_field": {
      "access_type": "read_write",
      "created_at": "2026-03-02T10:00:00+00:00",
      "description": "The mill's stated composition.",
      "field_type": "string",
      "id": "123",
      "is_active": true,
      "is_editable": true,
      "is_value_editable": true,
      "name": "Fabric composition",
      "source": "tightly",
      "updated_at": "2026-08-14T08:21:00+00:00"
    }
  },
  "message": {
    "desc": "OK",
    "service": "variant",
    "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, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 404 No custom field in this organisation carries that id.
  • 429

Try it in the reference

What changed on the catalogue ​

GET /api/v1/variants/events

Scopes: products:read

The change log of the catalogue: one entry per recorded change on any variant, each with id, variant_id, event_type, event_date, previous_value, new_value, a typed metadata object, and the product_name, variant_name and sku the change happened to. created_at, created_by_user_id and location_id are null where the change came from a sync rather than a person or a place.

OptionalInWhat it is
event_typequeryComma-separated event types to filter by. Omit to return every type.
start_datequeryStart of the range (ISO 8601 date). Unset reads from the beginning of what is kept.
end_datequeryEnd of the range (ISO 8601 date). Defaults to today.
limitqueryEvents in this page.
offsetqueryEvents to skip before this page.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/variants/events" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "events": [
      {
        "created_at": "2026-08-19T09:14:00+00:00",
        "created_by_user_id": null,
        "event_date": "2026-08-19",
        "event_type": "price_change",
        "id": "evt_9f21c40b",
        "location_id": null,
        "metadata": {
          "currency": "USD"
        },
        "new_value": "129.00",
        "previous_value": "119.00",
        "product_name": "Alpine Wool Overshirt",
        "sku": "AWO-CHR-S",
        "variant_id": "42318902722609",
        "variant_name": "Charcoal / S"
      },
      {
        "created_at": "2026-08-21T02:10:00+00:00",
        "created_by_user_id": null,
        "event_date": "2026-08-21",
        "event_type": "stockout",
        "id": "evt_9f21c512",
        "location_id": "61240442",
        "metadata": {
          "location_changes": [
            {
              "location_id": "61240442",
              "new": 0,
              "previous": 18
            }
          ]
        },
        "new_value": "0",
        "previous_value": "18",
        "product_name": "Alpine Wool Overshirt",
        "sku": "AWO-SND-S"
        …
      }
    ]
  }
}

What it refuses

  • 400 A date could not be read, or offset or limit is outside its range.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 429

Try it in the reference

A page of variants ​

GET /api/v1/variants/table

Scopes: products:read

A page of variants with the fields a catalogue sync needs per SKU: variant_id and its product_id, product and variant title, sku, vendor, category, description, image, product_status, gtin, unit_cost, sell_price, currency, sales_channels, selected_options, the physical fields (weight, length, width, height, uom, weight_unit), hs_code, country_of_origin, and last_updated_at and last_updated_by.

OptionalInWhat it is
offsetqueryRows to skip before this page.
limitqueryRows in this page.
searchqueryFree text over variant title, SKU, product title and vendor.
filter_argsqueryA JSON array of {key, operation, value}.
sort_argsqueryComma-separated sort columns, - for descending and + or nothing for ascending.
exportqueryAnswer a download URL for the filtered set instead of a page of rows.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/variants/table" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "filtered_max_size": 3288,
    "max_size": 14204,
    "offset": 0,
    "rows": [
      {
        "category": "Outerwear",
        "country_of_origin": "PT",
        "currency": "USD",
        "description": "A brushed wool overshirt cut for layering.",
        "gtin": "05012345678900",
        "height": null,
        "hs_code": "620331",
        "image_url": "https://cdn.tightly.io/p/7412095483953.jpg",
        "last_updated_at": "2026-09-01T11:42:00+00:00",
        "last_updated_by": "ana.ruiz@example.com",
        "length": null,
        "product_id": "7412095483953",
        "product_status": "ACTIVE",
        "product_title": "Alpine Wool Overshirt",
        "sales_channels": [
          {
            "id": "61240442",
            "name": "Online Store"
          }
        ],
        "selected_options": {
          "Colour": "Charcoal",
          "Size": "S"
        },
        "sell_price": 129,
        "sku": "AWO-CHR-S",
        "unit_cost": 34,
        "uom": "each",
        "variant_id": "42318902722609",
        "variant_title": "Charcoal / S",
        "vendor": "Veja",
        "weight": 0.62,
        "weight_unit": "kg"
        …
      }
    ]
  }
}

What it refuses

  • 400 offset or limit is outside its range, or filter_args is not valid JSON.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, because telling a caller which is which maps the surface for them.
  • 403 scope_missing: "This key cannot read Products." The key may not reach this operation. scope_missing when the key does not hold Products; ip_not_allowed when the caller's address is outside the key's allowlist. plan_excludes when the organisation's plan does not include the public API itself, which is sold with Essentials and is asked of every key minted under that gate. A key minted before the gate existed is never refused by it, whatever the plan holds. Products is ungated, so that is the only plan reason served here.
  • 429

Try it in the reference

Tightly API, version 2026-11.