Skip to content

API — Products & Inventory ​

Products are batch-priced inventory. A Product aggregates physical Batches (lots); the batch is the source of truth for quantity, cost, sale price, quality metrics, GI, and photos.

Products ​

MethodPathGatingNotes
GET/productsrequireActiveUserOwn products only.
POST/productsrequireActiveUserTaxonomy leaf + quality metrics + first-lot cost. Server derives name from the leaf — client sending name → 422. Accepts source (OWN/PURCHASED) + costPerKg.
GET/products/:idrequireActiveUser
PATCH/products/:idrequireActiveUser + ownership
DELETE/products/:idrequireActiveUser + ownershipSoft-delete (row + ledger preserved).
POST/products/:id/buyrequireActiveUserBuy Now — atomic direct-book. batchId required. Charges the lot's sale price. Creates a DIRECT_BOOKED SellLead + ACCEPTED Offer + Order + LeadView.
POST/products/:id/buy/:offerId/cancelrequireActiveUserPost-facto cancel within the grace window (no quota refund; trust penalty).
GET/products/:id/available-batchesrequireAuthBuyer-safe lot list (id, sale price, available qty, GI, photos, metrics — no cost).
GET/products/:id/pnlrequireActiveUser + ownershipOwner-only realised P&L + inventory valuation + unresolved sales.
POST/products/:id/photosrequireActiveUser + ownershipMultipart → Sharp → R2. Cap 10/product.
DELETE/products/:id/photos/:photoIdownership
PATCH/products/:id/photos/reorderownership{ orderedIds: string[] }.

Batches ​

MethodPathGatingNotes
PATCH/batches/:id/priceownershipUpdate salePricePerKg (no inventory event).
PATCH/batches/:id/production-costownershipOwner-only editable production cost (≥0).
PATCH/batches/:id/acquired-costownershipOwner-only editable buy cost (≥0).
DELETE/batches/:idownershipSoft-delete. Guards: not reserved, not on an active lead, not the last lot, no live stock (409 BATCH_HAS_STOCK).
POST / DELETE / PATCH/batches/:id/photos[...]ownershipPer-lot photos (cap 5).
POST / DELETE/batches/:id/gi-certificateownershipGI certificate (image or PDF), GI-tagged lots only.

Add-stock, transform, and off-platform-sale flows are additional batch surfaces (see the /batches routes and docs/flows/end-to-end-trading-flow.md).

Pricing model ​

  • Batch.salePricePerKg — the price a specific lot sells at. Buy Now charges this.

  • displayPricePerKg — weighted average over in-stock lots (shown publicly):

    text
    displayPricePerKg = Σ(lot.salePricePerKg × lot.currentQtyKg) / Σ(lot.currentQtyKg)
  • Product.referencePricePerKg — informational legacy only; never charged or shown.

Owner-only vs public fields ​

Cost is private

Buy cost, production cost, cost basis, blended cost, realised P&L, inventory valuation, and supplier details are owner-only — never returned by any counterparty-facing serializer (available-batches, lead/board rows, serializeBatchForLead). Guard tests enforce this.

Public (visible to any buyer): sale price, GI status/region, quality metrics, photos, and the statutory GST bracket (gstRate fraction + hsnCode).

GST ​

Each product resolves its GST bracket from the taxonomy (nearest-ancestor-wins). gstRate is a fraction (0.05 = 5%); 0 is a real exempt bracket, null means "no bracket". An Order snapshots the rate at accept time — a later bracket edit never rewrites a past deal, but product/lead displays read the live bracket.

Inventory ledger ​

InventoryEvent is append-only and batch-anchored (every event has a non-null batchId). Sale events (SELL_ACCEPTED, BUY_ACCEPTED, OFF_PLATFORM_SELL, Buy Now) stamp realised { salePricePerKg, costPerKg }. Read the timeline via the batch surfaces; filter by lot with ?batchId=.

Internal technical documentation — Cropto