Skip to content

Catalogue

Products, cost prices and the rankings derived from them.

A product is identified by the pair (source, sku). The same physical item listed on both marketplaces is two separate products, with separate cost prices. See Data model.


List products

GET /api/products/list

Every product in the catalogue, ordered by marketplace then name. Takes no parameters and is not paginated.

Field Type Description
source string wb or ozon
sku integer Marketplace SKU
name string Product name as supplied by the marketplace
category string | null Category, if the marketplace provided one
cost_price number | null Your cost per unit, or null if not set

This is the endpoint to use when auditing which products still need a cost price.

curl http://localhost:8000/api/products/list

Update cost price

PATCH /api/products/{source}/{sku}/cost

Sets the cost price for one product. This is the only value in the catalogue you own; everything else comes from the marketplace and is overwritten on the next sync.

Path parameters

Parameter Type Description
source string wb or ozon
sku integer Marketplace SKU

Query parameters

Parameter Type Required Description
cost_price number yes Cost per unit

Response

{ "ok": true }

Errors

Status Cause
400 source is not wb or ozon
422 sku or cost_price could not be parsed as a number
curl -X PATCH "http://localhost:8000/api/products/wb/123456789/cost?cost_price=450"

A SKU that does not exist is not an error

The update runs as a SQL statement matched on (source, sku). If nothing matches, zero rows are affected and the response is still {"ok": true}. Confirm the change by reading the product back rather than by trusting the response.

Changing a cost price affects all historical profit figures for that product immediately, because profit is computed at query time rather than stored. There is no concept of a cost price that applies from a given date.


Profitability

GET /api/profitability

Per-product economics over the full history, ordered by gross profit. Takes no parameters.

Field Type Description
sku integer Marketplace SKU
source string wb or ozon
name string Product name
category string | null Category
cost_price number | null Your cost per unit
units_sold integer Units
revenue number Before deductions
commission number Marketplace commission
logistics number Logistics cost
gross_profit number Net revenue minus cost of goods
roi_pct number | null Gross profit as a percentage of money spent on goods

roi_pct is null for any product with no cost price, because the denominator is zero. It is not 0, and treating it as such produces a misleading ranking.


ABC classification

GET /api/abc

Assigns each product a class by its cumulative share of profit over the last 30 days. Takes no parameters.

Field Type Description
sku integer Marketplace SKU
source string wb or ozon
name string Product name
category string | null Category
profit_30d number Profit over the last 30 days
cumulative_pct number Running share of total profit, descending
abc_class string A, B or C

Products are sorted by profit descending, and the class boundaries fall where the running total crosses the conventional 80% and 95% thresholds. A product that made no profit in the window still receives a class.


Rankings

GET /api/top

Best or worst performing products over a window.

Parameter Type Default Description
metric string profit profit, revenue, units or flop
days integer 30 Lookback window
limit integer 20 Maximum rows returned
source string all wb, ozon or all

flop ranks by profit ascending, returning your worst performers. Any other unrecognised value falls back to ranking by profit descending.

Response

Field Type Description
sku integer Marketplace SKU
source string wb or ozon
name string Product name
category string | null Category
units_sold integer Units in the window
revenue number Before deductions
commission number Marketplace commission
logistics number Logistics cost
gross_profit number After deductions and cost of goods
roi_pct number | null Return on cost of goods
curl "http://localhost:8000/api/top?metric=flop&days=90&limit=10"

Rows with a null sort value are placed last regardless of direction, so a flop query does not fill up with products that simply have no cost price recorded.