Supercommerce API Docs
Vendor API

Analytics Module — Vendor surface

Vendor-facing HTTP surface for precomputed dashboard snapshots (sales, earnings, fulfilment, inventory KPIs for today / 7d / 30d / all-time) and a live SSE paid-order feed.

Vendor-facing HTTP surface for precomputed dashboard snapshots (sales, earnings, fulfilment, inventory KPIs) and a live SSE order feed. The dashboard is computed every 10 minutes by a background worker and served directly from a snapshot table — it is never computed on request. The SSE stream is a long-lived connection that emits a baseline count on connect and increments as new paid orders arrive.

Source: api-modules/vendor-analytics/src/controllers/vendor-analytics.controller.ts, api-modules/vendor-analytics/src/controllers/vendor-live-orders.controller.ts.


Conventions

Authentication

Both endpoints require a Better-Auth bearer session with an active vendor.

Authorization: Bearer <session-token>

The active vendor is resolved via resolveActiveVendorId(session) — prefers session.session.activeOrganizationId (better-auth canonical) and falls back to activeVendorId. Sessions missing an active vendor are rejected with 403 Forbidden (FORBIDDEN).

There is no platform RBAC permission guard on this surface — vendor-session scoping is the only access gate.

Tenant scoping

Every read scopes to the active vendor's id. Cross-vendor data is never exposed.

Response envelope (dashboard)

{
  "data": { /* dashboard payload */ },
  "message": "Success",
  "statusCode": 200
}

SSE stream (live/orders)

The SSE endpoint is not wrapped by the standard response envelope. It is a raw text/event-stream connection; each frame is a data: line containing a JSON object.

Error envelope

statusCodeerrorCode examples
400BAD_REQUEST, VALIDATION_ERROR
401UNAUTHORIZED
403FORBIDDEN (no active vendor)
500INTERNAL_SERVER_ERROR

Money + dates

All monetary fields are integer subunits (paise / cents / eurocents — single currency per tenant). Date/time fields are ISO 8601 strings (computedAt, at, bucket).


Endpoints

GET /vendor/analytics/dashboard

Returns the vendor's precomputed analytics snapshot for the selected time window.

The snapshot is computed every 10 minutes by a background BullMQ worker (vendor-analytics-refresh job). It is never recalculated on request. Before the worker has run for the first time — or for a brand-new vendor with no data — every metric is zeroed and computedAt is null.

Query parameters

ParameterTypeDefaultDescription
window"today" | "7d" | "30d" | "all_time""30d"The analytics time window to serve

Response fields (data object)

FieldTypeDescription
windowstringThe window requested
computedAtstring | nullISO datetime of the last snapshot computation, or null if never computed
grossSalesint (subunits)Total order revenue in the window
netEarningsint (subunits)Revenue after commission deduction
commissionPaidint (subunits)Commission withheld by the platform
refundsAmountint (subunits)Total value of refunds processed
ordersCountintTotal orders (paid) in the window
unitsSoldintTotal line-item units sold
aovint (subunits)Average order value (grossSales / ordersCount, 0 when ordersCount = 0)
deliveredOrdersintSub-orders in delivered status
cancelledOrdersintSub-orders in cancelled status
avgFulfillSecondsintAverage seconds from order placed to fulfilled
avgDeliverSecondsintAverage seconds from fulfilled to delivered
productsActiveintActive product variants
productsDraftintDraft product variants
productsArchivedintArchived product variants
skusLowStockintSKUs below their low-stock threshold
skusOutOfStockintSKUs with zero on-hand inventory
inventoryUnitsOnHandintTotal on-hand units across all SKUs
pendingActionCountintSub-orders awaiting vendor action (pending status)
salesTrendTrendBucket[]Revenue + order count over time (see below)
statusBreakdownStatusBreakdownSub-order counts by status (see below)
topProductsByRevenueTopProduct[] (max 5)Top 5 SKUs ranked by revenue
topProductsByUnitsTopProduct[] (max 5)Top 5 SKUs ranked by units sold
lowStockListLowStockItem[] (max 20)Up to 20 SKUs closest to or past their low-stock threshold
deltasDeltas | nullPeriod-over-period changes; null for all_time (no previous period)

TrendBucket

Bucket granularity depends on the window:

  • today → hourly buckets
  • 7d, 30d → daily buckets
  • all_time → monthly buckets
{
  bucket: string;   // ISO datetime (start of the bucket)
  revenue: number;  // integer subunits
  orders: number;
}

StatusBreakdown

{
  pending: number;
  fulfilled: number;
  delivered: number;
  cancelled: number;
  returned: number;
}

TopProduct

{
  variantId: string;
  productId: string | null;
  name: string;
  sku: string;
  units: number;
  revenue: number;  // integer subunits
}

LowStockItem

{
  variantId: string;
  sku: string;
  name: string;
  onHand: number;
  threshold: number | null;
}

Deltas

Holds period-over-period comparison for the four headline KPIs. Each delta has an absolute value (in the same unit as the metric) and a pct (percentage change, or null if the previous period had a zero baseline so percentage is undefined). The deltas field itself is null for the all_time window because there is no defined previous period.

{
  grossSales:   { absolute: number; pct: number | null };
  netEarnings:  { absolute: number; pct: number | null };
  ordersCount:  { absolute: number; pct: number | null };
  unitsSold:    { absolute: number; pct: number | null };
}

Example response

// GET /vendor/analytics/dashboard?window=7d
{
  "data": {
    "window": "7d",
    "computedAt": "2026-06-08T09:40:00.000Z",
    "grossSales": 18750000,
    "netEarnings": 15937500,
    "commissionPaid": 2812500,
    "refundsAmount": 350000,
    "ordersCount": 124,
    "unitsSold": 198,
    "aov": 151209,
    "deliveredOrders": 98,
    "cancelledOrders": 7,
    "avgFulfillSeconds": 14400,
    "avgDeliverSeconds": 172800,
    "productsActive": 42,
    "productsDraft": 5,
    "productsArchived": 3,
    "skusLowStock": 4,
    "skusOutOfStock": 1,
    "inventoryUnitsOnHand": 831,
    "pendingActionCount": 19,
    "salesTrend": [
      { "bucket": "2026-06-02T00:00:00.000Z", "revenue": 2100000, "orders": 14 },
      { "bucket": "2026-06-03T00:00:00.000Z", "revenue": 2450000, "orders": 16 },
      { "bucket": "2026-06-04T00:00:00.000Z", "revenue": 3200000, "orders": 21 },
      { "bucket": "2026-06-05T00:00:00.000Z", "revenue": 2800000, "orders": 18 },
      { "bucket": "2026-06-06T00:00:00.000Z", "revenue": 3100000, "orders": 20 },
      { "bucket": "2026-06-07T00:00:00.000Z", "revenue": 2750000, "orders": 18 },
      { "bucket": "2026-06-08T00:00:00.000Z", "revenue": 350000,  "orders": 17 }
    ],
    "statusBreakdown": {
      "pending": 19,
      "fulfilled": 0,
      "delivered": 98,
      "cancelled": 7,
      "returned": 0
    },
    "topProductsByRevenue": [
      { "variantId": "var_01", "productId": "prod_01", "name": "Vitamin C Serum 30ml", "sku": "VCS-30", "units": 28, "revenue": 4200000 },
      { "variantId": "var_02", "productId": "prod_02", "name": "Retinol Night Cream", "sku": "RNC-50", "units": 17, "revenue": 3400000 },
      { "variantId": "var_03", "productId": "prod_01", "name": "Vitamin C Serum 15ml", "sku": "VCS-15", "units": 31, "revenue": 2170000 },
      { "variantId": "var_04", "productId": "prod_03", "name": "Hyaluronic Toner", "sku": "HYA-T", "units": 12, "revenue": 1800000 },
      { "variantId": "var_05", "productId": "prod_04", "name": "SPF 50 Sunscreen", "sku": "SPF-50", "units": 22, "revenue": 1540000 }
    ],
    "topProductsByUnits": [
      { "variantId": "var_03", "productId": "prod_01", "name": "Vitamin C Serum 15ml", "sku": "VCS-15", "units": 31, "revenue": 2170000 },
      { "variantId": "var_01", "productId": "prod_01", "name": "Vitamin C Serum 30ml", "sku": "VCS-30", "units": 28, "revenue": 4200000 },
      { "variantId": "var_05", "productId": "prod_04", "name": "SPF 50 Sunscreen", "sku": "SPF-50", "units": 22, "revenue": 1540000 },
      { "variantId": "var_02", "productId": "prod_02", "name": "Retinol Night Cream", "sku": "RNC-50", "units": 17, "revenue": 3400000 },
      { "variantId": "var_06", "productId": "prod_05", "name": "Peptide Eye Cream", "sku": "PEC-15", "units": 13, "revenue": 1430000 }
    ],
    "lowStockList": [
      { "variantId": "var_07", "sku": "MZ-CLN-100", "name": "Micellar Cleanser 100ml", "onHand": 3, "threshold": 10 },
      { "variantId": "var_08", "sku": "RET-EYE",    "name": "Retinol Eye Serum",      "onHand": 0, "threshold": 5 },
      { "variantId": "var_09", "sku": "GLW-MASK",   "name": "Glow Sheet Mask",        "onHand": 4, "threshold": 8 },
      { "variantId": "var_10", "sku": "NIAC-SER",   "name": "Niacinamide Serum 30ml", "onHand": 2, "threshold": 10 }
    ],
    "deltas": {
      "grossSales":  { "absolute": 1250000, "pct": 7.14 },
      "netEarnings": { "absolute": 1062500, "pct": 7.14 },
      "ordersCount": { "absolute": 11,      "pct": 9.74 },
      "unitsSold":   { "absolute": 17,      "pct": 9.39 }
    }
  },
  "message": "Success",
  "statusCode": 200
}

GET /vendor/analytics/live/orders (Server-Sent Events)

Opens a long-lived SSE connection (text/event-stream) that streams real-time paid-order activity to the vendor.

This endpoint is not wrapped in the standard response envelope — the response body is a raw event stream.

Behaviour

EventWhen emittedPayload
Immediately on (re)connectbaseline snapshot of today's paid-order countsnapshot frame
On each newly-paid order containing this vendor's line itemsincremented live countorder.paid frame
Every 30 secondskeep-aliveheartbeat frame

Self-healing counter: on every connect (including EventSource auto-reconnects after a dropped connection), the controller re-baselines the count from a fresh DB query. This means a missed event corrects itself within one reconnect cycle — the count never permanently drifts.

Frame shapes

All frames arrive as data: <JSON>\n\n lines.

snapshot — emitted once immediately on connect:

{
  "type": "snapshot",
  "paidOrdersToday": 12,
  "at": "2026-06-08T09:41:05.123Z"
}

order.paid — emitted for each newly-paid order that includes this vendor's items:

{
  "type": "order.paid",
  "paidOrdersToday": 13,
  "orderNumber": "ORD-2026-00892",
  "vendorTotal": 249900,
  "at": "2026-06-08T09:53:28.447Z"
}

vendorTotal is the vendor's slice of that order in integer subunits.

heartbeat — emitted every 30 seconds:

{
  "type": "heartbeat",
  "at": "2026-06-08T10:00:05.001Z"
}

Example: curl

curl -N \
  -H "Authorization: Bearer <session-token>" \
  -H "Accept: text/event-stream" \
  https://api.example.com/vendor/analytics/live/orders

Example stream output:

data: {"type":"snapshot","paidOrdersToday":12,"at":"2026-06-08T09:41:05.123Z"}

data: {"type":"heartbeat","at":"2026-06-08T09:41:35.001Z"}

data: {"type":"order.paid","paidOrdersToday":13,"orderNumber":"ORD-2026-00892","vendorTotal":249900,"at":"2026-06-08T09:53:28.447Z"}

data: {"type":"heartbeat","at":"2026-06-08T09:54:05.002Z"}

On this page