Skip to content

Sales ​

15 reads, no writes, on train 2026-11. Scopes: sales:read.

OperationMethodScopePath
Get sales analyticsGETsales:read/api/v1/sales/analytics
Get sales filtersGETsales:read/api/v1/sales/filters
Net sales at category × channel type × periodGETsales:read/api/v1/sales/net-sales
Get sales new overviewGETsales:read/api/v1/sales/new-overview
Get sales channel performanceGETsales:read/api/v1/sales/performance/channels
Get sales tableGETsales:read/api/v1/sales/table
Get sales velocity eventGETsales:read/api/v1/sales/velocity/events/{event_id}
Get event analyticsGETsales:read/api/v1/sales/velocity/events/{event_id}/analytics
Get suggested eventsGETsales:read/api/v1/sales/velocity/events/suggested
Get sales velocity events tableGETsales:read/api/v1/sales/velocity/events/table
Get sales velocity filtersGETsales:read/api/v1/sales/velocity/filters
Get forecast model configGETsales:read/api/v1/sales/velocity/forecast-config
Get sales velocity tableGETsales:read/api/v1/sales/velocity/table
Get sales velocity table totalsGETsales:read/api/v1/sales/velocity/table/totals
Get variant bundle contributionsGETsales:read/api/v1/sales/velocity/variants/{variant_id}/bundle-contributions

Get sales analytics ​

GET /api/v1/sales/analytics

Scopes: sales:read

Revenue trend and period-over-period comparison, grouped: each group carries revenue_total and revenue_monthly keyed by month, with Overall beside the groups.

RequiredInWhat it is
start_datequeryStart date for analytics period (ISO date format)
end_datequeryEnd date for analytics period (ISO date format)
OptionalInWhat it is
in_fiscal_yearquerythis or last. Shortcut for getting this fiscal year's or last fiscal year's data. If not provided, start_date and end_date are expected.
categoriesqueryComma separated categories to filter the budget
fieldsqueryComma separated fields to be fetched
is_samplequeryWhen true, returns sample/demo data instead of real analytics
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/analytics?start_date=<start_date>&end_date=<end_date>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "Overall": {
      "revenue_monthly": {
        "2026-01-01": 164000,
        "2026-02-01": 171200
      },
      "revenue_total": 984000
    },
    "cash cows": {
      "revenue_monthly": {
        "2026-01-01": 68800,
        "2026-02-01": 71400
      },
      "revenue_total": 412800
    }
  },
  "message": {
    "desc": "",
    "service": "sales",
    "severity": "INFO"
  }
}

What it refuses

  • 400 Bad Request - Invalid request parameters or validation errors
  • 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 Sales." 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

Try it in the reference

Get sales filters ​

GET /api/v1/sales/filters

Scopes: sales:read

The values the sales table can be filtered on: categories, tags and custom fields as flat lists. Send field to page one field's values with limit, offset and search, which is what a long tag list needs; omit it and every filter comes back at once. Read it to build filter_args for Get sales table out of values that exist.

OptionalInWhat it is
fieldqueryOptional field name to retrieve paginated values (e.g., 'shopify_tags'). Omit to get all filters.
searchqueryOptional search term to filter results (only applicable when field is provided)
offsetqueryNumber of items to skip for pagination (only applicable when field is provided)
limitqueryMaximum number of items to return per page (only applicable when field is provided)
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/filters" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "categories": [
      "Knitwear",
      "Outerwear"
    ],
    "custom_fields": [
      "fabric_weight"
    ],
    "shopify_tags": [
      "core",
      "seasonal"
    ]
  },
  "message": {
    "desc": "",
    "service": "sales",
    "severity": "INFO"
  }
}

What it refuses

  • 400 Bad Request - Invalid request parameters or validation errors
  • 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 Sales." 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

Try it in the reference

Net sales at category × channel type × period ​

GET /api/v1/sales/net-sales

Scopes: sales:read

Net sales bucketed by category, channel type and period: one row per combination with net_sales and net_items_sold, plus the window, the grain, the calendar, a coverage block counting the orders and dimensions behind the rows, and a basis note. This is the coarse actuals feed a plan is measured against.

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
commitment_idqueryScope to this commitment's exact merchandise membership and dates.
grainqueryThe period the rows are bucketed into.
categoriesqueryComma-separated categories to filter to. Filters; never changes the grain.
channel_typesqueryComma-separated channel types to filter to.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/net-sales?start_date=<start_date>&end_date=<end_date>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "basis_note": "Net of refunds, over the organisation's own order book.",
    "calendar": "gregorian",
    "coverage": {
      "categories": 14,
      "channel_types": 3,
      "orders": 41208,
      "unattributed_orders": 0,
      "unattributed_reason": null
    },
    "grain": "month",
    "rows": [
      {
        "category": "Knitwear",
        "channel_type": "wholesale",
        "net_items_sold": 14020,
        "net_sales": 412800,
        "period_start": "2026-01-01"
      },
      {
        "category": "Knitwear",
        "channel_type": "direct",
        "net_items_sold": 2410,
        "net_sales": 188400,
        "period_start": "2026-01-01"
      }
    ],
    "window": {
      "end_exclusive": "2026-07-01",
      "start": "2026-01-01"
    }
  },
  "message": {
    "desc": "",
    "service": "sales",
    "severity": "INFO"
  }
}

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 query can strike.
  • 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 Sales." 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

Try it in the reference

Get sales new overview ​

GET /api/v1/sales/new-overview

Scopes: sales:read

The sales overview a dashboard is built from: cards carrying a value, the previous period's value and the change per measure, and charts carrying revenue (the measured series), historical (the requested range), forecast (the same length forward) and last_year (the same range shifted back 365 days).

OptionalInWhat it is
filter_argsqueryJSON array of filter conditions. Valid filter keys with their allowed operations and value types:
periodqueryAggregation granularity only.
cardsqueryComma-separated list of card metrics to include.
chartsqueryComma-separated list of charts to include. Available charts: revenue, sales_by_variant
product_optionqueryProduct option for filtering and analysis
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/new-overview" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "cards": [
    {
      "change": 244.59,
      "info": "Quantity Sold",
      "key": "sold_quantity",
      "previous_value": 379,
      "unit": "count",
      "value": 1306
    },
    {
      "change": 263.46,
      "info": "Sales Amount",
      "key": "total_revenue",
      "previous_value": 8685.65,
      "unit": "dollar",
      "value": 31569.22
    }
  ],
  "charts": {
    "forecast": [
      {
        "date": "2025-10-27",
        "sold_quantity": 18.09,
        "total_revenue": 408.35
      },
      {
        "date": "2025-11-03",
        "sold_quantity": 10.22,
        "total_revenue": 230.65
      }
    ],
    "historical": [
      {
        "date": "2025-07-28",
        "sold_quantity": 302,
        "total_revenue": 7001.44
      },
      {
        "date": "2025-08-04"
        …
      }
    ]
  }
}

What it refuses

  • 400 Bad Request - Invalid request parameters or validation errors
  • 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 Sales." 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

Try it in the reference

Get sales channel performance ​

GET /api/v1/sales/performance/channels

Scopes: sales:read

Revenue per sales channel over a window: each channel with its total revenue, the difference against the previous period of the same length, the change as a percentage, and a dated series at the requested granularity.

RequiredInWhat it is
start_datequeryStart date for the current period (ISO date format YYYY-MM-DD)
end_datequeryEnd date for the current period (ISO date format YYYY-MM-DD). Maximum range is 1 year from start_date.
OptionalInWhat it is
sales_channel_idsqueryComma-separated list of sales channel IDs to filter. If not provided, returns all channels.
granularityqueryTime granularity for the data points (day, week, or month)
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/performance/channels?start_date=<start_date>&end_date=<end_date>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "channels": [
      {
        "change_percentage": 12.5,
        "channel_name": "Amazon",
        "data": [
          {
            "date": "2025-11-01",
            "revenue": 45000
          },
          {
            "date": "2025-11-02",
            "revenue": 52000
          }
        ],
        "period_difference": 34000.25,
        "sales_channel_id": "ch_123",
        "total_revenue": 453000.5
      },
      {
        "change_percentage": 8.3,
        "channel_name": "Shopify",
        "data": [
          {
            "date": "2025-11-01",
            "revenue": 32000
          },
          {
            "date": "2025-11-02",
            "revenue": 35000
          }
        ],
        "period_difference": 54000,
        "sales_channel_id": "ch_456",
        "total_revenue": 324000
      }
    ]
  },
  "message": {
    …
  }
}

What it refuses

  • 400 Bad Request - Invalid parameters (e.g., date range exceeds 1 year, invalid date format)
  • 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 Sales." 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

Try it in the reference

Get sales table ​

GET /api/v1/sales/table

Scopes: sales:read

A page of sales per variant or per product: gross sales, discounts, returns, taxes, net sales, net items sold, returned units, return rate, sell-through rate, and the units and revenue lost to stockouts, with filtered_max_size for the size of the filtered set. A variant row also carries price_sensitivity, the pricing engine's tier, and price_sensitivity_absence, which says which blank a null tier is; a product row carries neither.

OptionalInWhat it is
limitqueryPagination limit
offsetqueryPagination offset
filter_argsqueryJSON array of filter conditions.
sort_argsqueryComma-separated list of sort arguments (e.g., "+sales_amount,-variant_title").
searchquerySearch term to filter sales
typequeryView type - 'variants' shows individual variants, 'products' shows grouped by product.
exportqueryIf true, returns an export URL instead of paginated table data
comparison_date_gtequeryStart of comparison date range.
comparison_date_ltequeryEnd of comparison date range. See comparison_date_gte.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/table" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "filtered_max_size": 4120,
    "offset": 0,
    "rows": [
      {
        "category": "Knitwear",
        "discounts": 2140,
        "gross_sales": 41880,
        "lost_sales_units": 40,
        "missed_revenue": 1180,
        "net_items_sold": 1280,
        "net_sales": 37760,
        "price_sensitivity": "MODERATE",
        "price_sensitivity_absence": null,
        "price_sensitivity_absence_word": null,
        "price_sensitivity_computed_at": "2026-09-01T06:31:00Z",
        "product_id": "4410092",
        "product_title": "Terry Crew",
        "return_rate": 0.048,
        "returned_units": 62,
        "returns": 1980,
        "sell_through_rate": 0.86,
        "shopify_tags": [
          "core"
        ],
        "sku": "TB-CREW-BLK-M",
        "taxes": 0,
        "total_sales": 37760,
        "variant_id": "44100920011",
        "variant_title": "Black / M"
      }
    ],
    "size": 1
  },
  "message": {
    "desc": "",
    "service": "sales",
    "severity": "INFO"
  }
  …
}

What it refuses

  • 400 Bad Request - Invalid request parameters or validation errors
  • 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 Sales." 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

Try it in the reference

Get sales velocity event ​

GET /api/v1/sales/velocity/events/{event_id}

Scopes: sales:read

One sales velocity event: its name and description, its window, the adjustment it applies and that adjustment's configuration, the channels it covers, its status and how many items and variants it holds.

RequiredInWhat it is
event_idpathUnique identifier of the sales velocity event
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/velocity/events/<event_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "adjustment_config": {
      "uplift_percent": 45
    },
    "adjustment_type": "uplift_percent",
    "awaiting_input": false,
    "created_at": "2026-08-02T11:20:00+00:00",
    "description": "Site-wide promotion",
    "end_date": "2026-11-30",
    "id": 412,
    "is_active": true,
    "is_overridden": false,
    "items_count": 184,
    "name": "Black Friday 2026",
    "sales_channel_id": "61240442",
    "sales_channels": [
      {
        "id": "61240442",
        "name": "Wholesale"
      }
    ],
    "start_date": "2026-11-27",
    "status": "scheduled",
    "suggested_event_key": null,
    "variants_count": 184
  },
  "message": {
    "desc": "",
    "service": "sales_velocity_events",
    "severity": "INFO"
  }
}

What it refuses

  • 400 Bad Request - Invalid event ID format
  • 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 Sales." 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.
  • 404 Not Found - Sales velocity event with specified ID does not exist
  • 429

Try it in the reference

Get event analytics ​

GET /api/v1/sales/velocity/events/{event_id}/analytics

Scopes: sales:read

How one sales velocity event performed: baseline (the forecast without it), target (the forecast adjusted for it) and actual units, revenue, profit and return on investment, with an accuracy figure.

RequiredInWhat it is
event_idpathUnique identifier of the sales velocity event
OptionalInWhat it is
group_byqueryGrouping dimension. Omit for a single summary row, or pass "date" for a row per day.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/velocity/events/<event_id>/analytics" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "group_by": "date",
    "rows": [
      {
        "accuracy": 78,
        "actual_profit": 180,
        "actual_revenue": 450,
        "actual_roi": 66.67,
        "actual_unit": 9,
        "baseline_profit": 210,
        "baseline_revenue": 525,
        "baseline_unit": 10.5,
        "group_by_col": "2025-12-01",
        "target_profit": 231,
        "target_revenue": 577.5,
        "target_roi": 66.67,
        "target_unit": 11.55
      }
    ]
  },
  "message": {
    "desc": "",
    "service": "sales_velocity_events",
    "severity": "SUCCESS"
  }
}

What it refuses

  • 400 Bad Request - Invalid group_by value
  • 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 Sales." 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.
  • 404 Not Found - Sales velocity event with specified ID does not exist
  • 429

Try it in the reference

Get suggested events ​

GET /api/v1/sales/velocity/events/suggested

Scopes: sales:read

The events this organisation's industry is offered for a year, such as Black Friday: each with the real event window, a recommended preparation window (suggested_start_date and suggested_end_date), the lift range to expect, and whether it has already been dismissed.

OptionalInWhat it is
yearqueryYear to retrieve suggested events for. Defaults to the current year.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/velocity/events/suggested" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "events": [
      {
        "adjustment_max": 50,
        "adjustment_min": 30,
        "end_date": "2026-11-27",
        "event_key": "black_friday_2026",
        "is_dismissed": false,
        "name": "Black Friday",
        "start_date": "2026-11-27",
        "suggested_end_date": "2026-11-27",
        "suggested_start_date": "2026-11-13"
      },
      {
        "adjustment_max": 40,
        "adjustment_min": 25,
        "end_date": "2026-11-30",
        "event_key": "cyber_monday_2026",
        "is_dismissed": false,
        "name": "Cyber Monday",
        "start_date": "2026-11-30",
        "suggested_end_date": "2026-11-30",
        "suggested_start_date": "2026-11-27"
      }
    ]
  },
  "message": {
    "desc": "",
    "service": "sales_velocity_events",
    "severity": "INFO"
  }
}

What it refuses

  • 400 Bad Request - Invalid parameters
  • 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 Sales." 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

Try it in the reference

Get sales velocity events table ​

GET /api/v1/sales/velocity/events/table

Scopes: sales:read

A page of sales velocity events with their windows, statuses, adjustment types and item counts. Page with limit and offset, narrow with filter_args and search, order with sort_args.

OptionalInWhat it is
limitqueryNumber of rows to retrieve in the response.
offsetqueryNumber of rows to skip in the response.
filter_argsqueryJSON array of filter conditions.
sort_argsqueryComma-separated list of sort arguments.
searchquerySearch keyword to filter event names or descriptions.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/velocity/events/table" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "awaiting_input_count": 2,
    "filtered_max_size": 12,
    "max_size": 12,
    "offset": 0,
    "rows": [
      {
        "adjustment_type": "uplift_percent",
        "awaiting_input": false,
        "end_date": "2026-11-30",
        "exclude_from_training": false,
        "id": 412,
        "items_count": 184,
        "name": "Black Friday 2026",
        "start_date": "2026-11-27",
        "status": "scheduled",
        "variants_count": 184
      }
    ],
    "size": 1,
    "status_counts": {
      "finished": 7,
      "running": 1,
      "scheduled": 4
    }
  },
  "message": {
    "desc": "",
    "service": "sales_velocity_events",
    "severity": "INFO"
  }
}

What it refuses

  • 400 Bad Request - Invalid request parameters or validation errors
  • 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 Sales." 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

Try it in the reference

Get sales velocity filters ​

GET /api/v1/sales/velocity/filters

Scopes: sales:read

The values the sales velocity table can be filtered on: locations and suppliers as id and name, events, the modified-status words, tags, and {min, max} ranges for the numeric columns. Send field to page one field's values with limit, offset and search; omit it and every filter comes back at once.

OptionalInWhat it is
fieldqueryOptional field name to retrieve paginated values (e.g., 'shopify_tags'). Omit to get all filters.
searchqueryOptional search term to filter results (only applicable when field is provided)
offsetqueryNumber of items to skip for pagination (only applicable when field is provided)
limitqueryMaximum number of items to return per page (only applicable when field is provided)
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/velocity/filters" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "event_id": [
      {
        "id": "412",
        "name": "Black Friday 2026"
      }
    ],
    "location_id": [
      {
        "id": "loc_0004",
        "name": "London DC"
      }
    ],
    "modified_status": [
      "all",
      "modified",
      "unmodified"
    ],
    "sales_velocity_30_days_range": [
      0,
      18.4
    ],
    "shopify_tags": [
      "core",
      "seasonal"
    ],
    "supplier": [
      {
        "supplier_id": "sup_0031",
        "supplier_name": "Porto Knits"
      }
    ]
  },
  "message": {
    "desc": "",
    "service": "sales_velocity",
    "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 Sales." 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

Try it in the reference

Get forecast model config ​

GET /api/v1/sales/velocity/forecast-config

Scopes: sales:read

The forecast model this organisation has selected and the configuration behind it: the model, the minimum sales and minimum weeks with sales a variant needs to be forecast, the base and pattern filters, the time windows, and any model-specific parameters under model_parameters.

OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/velocity/forecast-config" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "min_total_sales": 30,
    "min_weeks_with_sales": 8,
    "model": "auto_arima"
  },
  "message": {
    "desc": "",
    "service": "sales_velocity",
    "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 Sales." 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

Try it in the reference

Get sales velocity table ​

GET /api/v1/sales/velocity/table

Scopes: sales:read

A page of sales velocity per variant or product, the rate demand forecasting and days of cover are computed from: total demand and total sales for the window, the forecast model chosen for the row, whether it is seasonal, price, channel, supplier, bundle count and a confidence tier, with confidence_tier_counts for the filtered book.

OptionalInWhat it is
limitqueryNumber of rows to retrieve in the response.
offsetqueryNumber of rows to skip in the response.
filter_argsqueryJSON array of filter conditions.
sort_argsqueryComma-separated list of sort arguments.
searchquerySearch keyword to filter product or variant titles.
exportqueryIf true, returns an export URL instead of paginated table data
start_datequeryStart date for sales velocity analysis period
end_datequeryEnd date for sales velocity analysis period
typequeryView type - 'variants' shows individual variants, 'products' shows grouped by product.
demand_sales_columnsqueryComma-separated list of demand/sales columns to include.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/velocity/table" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "confidence_tier_counts": {
      "high": 1840,
      "low": 640,
      "medium": 1204,
      "new": 436,
      "scored": 3684,
      "total": 4120
    },
    "data_source": "ch",
    "demand_as_of": "2026-09-04T02:41:00+00:00",
    "filtered_max_size": 4120,
    "filtered_variants_size": 1284,
    "max_size": 4120,
    "offset": 0,
    "row_grain": "variants",
    "rows": [
      {
        "bundle_count": 2,
        "confidence_tier": "high",
        "forecast_model": "auto_arima",
        "is_modified": false,
        "is_seasonal": true,
        "price": 79,
        "product_id": "4410092",
        "product_title": "Terry Crew",
        "sales_channel_id": "61240442",
        "sales_channel_name": "Wholesale",
        "shopify_tags": [
          "core"
        ],
        "sku": "TB-CREW-BLK-M",
        "supplier_id": "sup_0031",
        "supplier_name": "Porto Knits",
        "total_demand": 412,
        "total_sales": 388,
        "typical_miss_pct": 0.14,
        "variant_id": "44100920011",
        "variant_title": "Black / M"
        …
      }
    ]
  }
}

What it refuses

  • 400 Bad Request - Invalid request parameters or validation errors
  • 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 Sales." 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

Try it in the reference

Get sales velocity table totals ​

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

Scopes: sales:read

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.

OptionalInWhat it is
filter_argsqueryJSON array of filter conditions.
searchquerySearch keyword to filter by product or variant title
start_datequeryStart date for the sales velocity analysis period
end_datequeryEnd date for the sales velocity analysis period. Must be provided together with start_date.
include_last_yearqueryWhen true, includes last year comparison data in the totals
aggregation_modequeryTime bucket for period columns. Auto-selected based on date range when omitted: daily (≤14 days), weekly (≤90 days), monthly (otherwise).
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/velocity/table/totals" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "data_source": "pg",
    "demand_as_of": "2026-09-04T02:41:00+00:00",
    "scope": {
      "pairs": 4120,
      "variants": 1284
    },
    "scope_unavailable_reason": null,
    "totals": [
      {
        "calculated_demand": 18820,
        "effective_demand": 19100,
        "last_period_sales": 17600,
        "max_date": "2026-09-13",
        "min_date": "2026-09-07",
        "sales": 18400,
        "sales_revenue": 984000,
        "week_grid": "MON:2026-09-07"
      }
    ],
    "week_grids": {
      "MON:2026-09-07": {
        "aligned": false,
        "days_in_next_planning_week": 1,
        "days_in_planning_week": 6,
        "planning_fiscal_week": 32,
        "planning_fiscal_year": 2026,
        "planning_week_end": "2026-09-12",
        "planning_week_start": "2026-09-06",
        "planning_week_start_dow": "SUN",
        "shift_days": 1,
        "week_end": "2026-09-13",
        "week_start": "2026-09-07",
        "week_start_dow": "MON"
      }
    }
  },
  "message": {
    "desc": ""
    …
  }
}

What it refuses

  • 400 Bad Request - invalid parameters
  • 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 Sales." 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

Try it in the reference

Get variant bundle contributions ​

GET /api/v1/sales/velocity/variants/{variant_id}/bundle-contributions

Scopes: sales:read

How much of one variant's demand comes from each bundle that contains it, over a window: one entry per bundle with the bundle's product and variant titles and its contribution, plus total_count and total_demand.

RequiredInWhat it is
variant_idpathThe component variant whose bundle contributions are requested.
sales_channel_idquerySales channel to scope the demand data to.
start_datequeryStart of the date range (inclusive).
end_datequeryEnd of the date range (inclusive).
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/sales/velocity/variants/<variant_id>/bundle-contributions?sales_channel_id=<sales_channel_id>&start_date=<start_date>&end_date=<end_date>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "bundle_contributions": [
      {
        "contribution": 42,
        "product_title": "Bundle Product A",
        "variant_title": "Bundle A"
      },
      {
        "contribution": 18,
        "product_title": "Bundle Product B",
        "variant_title": "Bundle Set B"
      },
      {
        "contribution": 8,
        "product_title": "Others",
        "variant_title": null
      }
    ],
    "data_source": "ch",
    "total_count": 3,
    "total_demand": 60
  },
  "message": {
    "service": "sales_velocity",
    "success": true
  }
}

What it refuses

  • 400 Bad Request - Invalid parameters
  • 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 Sales." 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

Try it in the reference

Tightly API, version 2026-11.