Skip to content

Inventory and listings

Stock levels, reorder priority, and how listings perform on the way to a sale.


Reorder list

GET /api/reorder

Every product with its current stock, how fast it is selling, and how long the stock will last. Takes no parameters.

Results are ordered by urgency first and then by remaining days, so the products that need attention are at the top without any client-side sorting.

Field Type Description
name string Product name
sku integer Marketplace SKU
source string wb or ozon
stock_qty integer Units currently in the marketplace's warehouses
avg_daily_sales number Mean units sold per day over the recent period
days_of_stock number | null stock_qty divided by avg_daily_sales
status string Urgency band

days_of_stock is null when the product has not sold recently, because dividing by a zero sales rate is undefined. Those rows sort last. This is correct: a product that is not selling is not about to run out.

The status bands are derived from days_of_stock against a typical restock lead time, not from a fixed unit threshold. A product selling 50 units a day with 200 in stock is more urgent than one selling 1 a day with 30 in stock, and the ordering reflects that.

curl http://localhost:8000/api/reorder

Listing performance

GET /api/cards

Daily statistics per listing: how many people saw it, clicked it, added it to a cart and ordered.

Parameter Type Default Description
days integer 30 Lookback window

Response, ordered by date descending then impressions descending.

Field Type Description
name string Product name
sku integer Marketplace SKU
source string wb or ozon
stat_date date The day these figures cover
views integer Impressions
clicks integer Clicks through to the listing
add_to_cart integer Cart additions
orders_count integer Orders placed
ctr_pct number Clicks as a percentage of impressions
cr_cart_pct number Cart additions as a percentage of clicks
ctr_grade string Qualitative band for the click-through rate

This endpoint returns one row per product per day, so a 30-day window across a large catalogue produces a lot of rows. Narrow the window rather than filtering client-side.


Conversion funnel

GET /api/funnel

The same journey aggregated over the whole window rather than broken out by day, with conversion computed at each step.

Parameter Type Default Description
days integer 30 Lookback window
source string all wb, ozon or all

Response, ordered by impressions descending.

Field Type Description
sku integer Marketplace SKU
source string wb or ozon
name string Product name
views integer Impressions
clicks integer Clicks
add_to_cart integer Cart additions
orders integer Orders
ctr number Clicks over impressions, as a percentage
click_to_cart number Cart additions over clicks, as a percentage
cart_to_order number Orders over cart additions, as a percentage
total_conv number Orders over impressions, as a percentage

Two filters are applied server-side

Only products with more than 100 impressions in the window are included, and at most 100 rows are returned.

The impression threshold exists because a conversion rate calculated on a handful of impressions is noise, and including it pushes actionable listings out of view. A newly published product will therefore be missing from this endpoint until it accumulates traffic. That is expected behaviour, not missing data.

Where a product name is unavailable, the response falls back to a generated label combining the marketplace and SKU, so the field is never null.

curl "http://localhost:8000/api/funnel?days=14&source=ozon"
[
  {
    "sku": 987654321,
    "source": "ozon",
    "name": "Thermal mug 450 ml",
    "views": 14820,
    "clicks": 612,
    "add_to_cart": 98,
    "orders": 41,
    "ctr": 4.13,
    "click_to_cart": 16.01,
    "cart_to_order": 41.84,
    "total_conv": 0.277
  }
]

Each conversion field guards against a zero denominator and returns 0 rather than failing, so a listing with impressions but no clicks reports ctr: 0 and zeros downstream.