Supercommerce API Docs
Vendor API

Cart Module — Vendor surface

Vendor-facing HTTP endpoints for browsing carts that contain the vendor's own products and for vendor-scoped cart funnel analytics. There are no write operations: a vendor cannot…

Vendor-facing HTTP endpoints for browsing carts that contain the vendor's own products and for vendor-scoped cart funnel analytics. There are no write operations: a vendor cannot mutate a customer's cart. Both surfaces are deliberately stripped of cross-vendor data, cart_token (the storefront bearer handle), and customer_id (PII) — a multi-vendor cart never leaks one vendor's volume to another.

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


Conventions

Authentication

All endpoints require a Better-Auth bearer session.

Authorization: Bearer <session-token>

The vendor scope is read from the session via resolveActiveVendorId(session) — it prefers session.session.activeOrganizationId (better-auth canonical) and falls back to activeVendorId. If neither is set, the request fails with 400 ACTIVE_VENDOR_REQUIRED.

Tenant scoping

Every read scopes to the active vendor's id:

  • GET /vendor/carts only returns carts that contain at least one line owned by the active vendor.
  • GET /vendor/carts/:id returns 404 when the cart exists but contains no lines owned by the active vendor — the cart's existence is itself confidential to non-participating vendors.
  • Analytics endpoints filter every metric to the active vendor's lines/events; the vendorId in each response carries the caller's id (never null on this surface).

Response envelope

Successful responses are wrapped by ResponseInterceptor:

{
  "data": <payload>,
  "message": "Success",
  "statusCode": 200,
  "metadata": { /* optional, e.g. pagination */ }
}

Error envelope

statusCodeerrorCode examples
400BAD_REQUEST, VALIDATION_ERROR, ACTIVE_VENDOR_REQUIRED
401UNAUTHORIZED
404NOT_FOUND
500INTERNAL_SERVER_ERROR, DATABASE_ERROR

Money

All monetary fields (vendorSubtotal, unitPrice, allocations[].amount, …) are integer subunits (paise / cents / eurocents) — single currency per tenant.


Domain types

CartVendorListItem

type CartVendorListItem = {
  cartId: string;
  status: "active" | "abandoned" | "converted" | "discarded";
  platform: "WEB" | "IOS" | "ANDROID";
  vendorLineCount: number;       // ONLY this vendor's lines
  vendorSubtotal: number;        // ONLY this vendor's lines (subunits)
  lastActivityAt: string;        // ISO
  createdAt: string;             // ISO
};

CartVendorView

Vendor-scoped projection of CartResponse. Strips cartToken, customerId, other vendors' bags, the global cartTotals, and limits appliedCouponAllocations to this vendor's allocation amount only.

type CartVendorView = {
  cartId: string;
  status: "active" | "abandoned" | "converted" | "discarded";
  platform: "WEB" | "IOS" | "ANDROID";
  vendorBag: {
    vendorId: string;
    lines: CartLineResponse[];   // see cart.md for full shape
    subtotal: number;            // subunits
    discountAllocated: number;   // subunits
    totalBeforeShippingAndTax: number;
    shipping: { cost: number } | null;
  };
  appliedCouponAllocations: Array<{
    code: string;
    amount: number;              // allocation against THIS vendor (subunits)
  }>;
  lastActivityAt: string;
  createdAt: string;
};

FunnelResponse

type FunnelBucket = {
  bucketAt: string;              // ISO start-of-bucket
  cartsCreated: number;
  itemsAdded: number;
  itemsRemoved: number;
  couponsApplied: number;
  giftsAttached: number;
  checkoutsPrepared: number;
  cartsAbandoned: number;
  uniqueActiveCarts: number;
};

type FunnelResponse = {
  granularity: "hourly" | "daily";
  vendorId: string;              // active vendor's id
  buckets: FunnelBucket[];
  totals: Omit<FunnelBucket, "bucketAt">;
};

AbandonmentResponse

type AbandonmentResponse = {
  vendorId: string;
  cartsCreated: number;
  cartsAbandoned: number;
  checkoutsPrepared: number;
  abandonmentRate: number;       // abandoned / created, 0..1 with 2 decimals
  checkoutRate: number;          // prepared / created,  0..1 with 2 decimals
};

TopVariantsResponse

type TopVariantsResponse = {
  vendorId: string;
  items: Array<{
    variantId: string;
    vendorId: string;
    removedCount: number;
  }>;
};

Vendor cart browse

Base path: /vendor/carts.

GET /vendor/carts — List vendor's carts

Returns carts containing at least one line owned by the active vendor.

Query

NameTypeDefaultNotes
status"active" | "abandoned" | "converted" | "discarded"?Filter by cart lifecycle status
customerIdstring?Filter to a specific customer
vendorIdstring?Ignored / redundant — scope is already the active vendor
activeSincestring? (ISO datetime)Inclusive lower bound for last_activity_at
limitint501..500
offsetint0>= 0

Response 200 — paginated envelope of CartVendorListItem[].

Errors

StatusCodeWhen
400ACTIVE_VENDOR_REQUIREDSession has no active vendor
400VALIDATION_ERRORQuery fails zod (bad enum, out-of-range pagination)

GET /vendor/carts/:id — Vendor-scoped detail

Returns a CartVendorView for a single cart.

Path params

NameTypeNotes
idstring (UUID)Must contain at least one line owned by the active vendor

Response 200 — envelope of CartVendorView.

{
  "data": {
    "cartId": "01J9...",
    "status": "active",
    "platform": "WEB",
    "vendorBag": {
      "vendorId": "01J9...",
      "lines": [ /* CartLineResponse[] for THIS vendor only */ ],
      "subtotal": 125000,
      "discountAllocated": 12500,
      "totalBeforeShippingAndTax": 112500,
      "shipping": { "cost": 4900 }
    },
    "appliedCouponAllocations": [
      { "code": "WELCOME10", "amount": 12500 }
    ],
    "lastActivityAt": "2026-05-07T10:00:00.000Z",
    "createdAt":      "2026-05-07T09:00:00.000Z"
  },
  "message": "Success",
  "statusCode": 200
}

Errors

StatusCodeWhen
400ACTIVE_VENDOR_REQUIREDSession has no active vendor
404NOT_FOUNDCart does not exist or contains no lines owned by the active vendor

Vendor cart analytics

Base path: /vendor/cart-analytics. Every endpoint accepts the common range query and reports metrics filtered to the active vendor's lines/events.

Common query — AnalyticsRangeQuery

NameTypeDefaultNotes
fromstring? (ISO datetime)30 days agoInclusive lower bound
tostring? (ISO datetime)nowExclusive upper bound
granularity"hourly" | "daily""daily"Bucket size

GET /vendor/cart-analytics/funnel — Vendor-scoped funnel rollup

Response 200FunnelResponse (vendor's vendorId populated).

{
  "data": {
    "granularity": "daily",
    "vendorId": "01J9...",
    "buckets": [ /* FunnelBucket[] */ ],
    "totals": { "cartsCreated": 1234, "itemsAdded": 5678, "itemsRemoved": 432, "couponsApplied": 89, "giftsAttached": 21, "checkoutsPrepared": 678, "cartsAbandoned": 345, "uniqueActiveCarts": 901 }
  }
}

GET /vendor/cart-analytics/abandonment — Abandonment + checkout rates

Response 200AbandonmentResponse.


GET /vendor/cart-analytics/top-abandoned-variants — Most-removed variants

Additional query

NameTypeDefaultNotes
limitint101..100

Response 200TopVariantsResponse (items are scoped to variants owned by the active vendor).


GET /vendor/cart-analytics/coupon-usage — Coupons applied to carts containing my products

Response 200

{
  "data": { "vendorId": "01J9...", "couponsApplied": 89 }
}

GET /vendor/cart-analytics/gift-attach-rate — Free-gift attach rate

Response 200

{
  "data": {
    "vendorId": "01J9...",
    "cartsCreated": 1234,
    "giftsAttached": 21,
    "rate": 0.02
  }
}

rate = giftsAttached / cartsCreated (0 when cartsCreated is 0), rounded to two decimal places.


  • cart (full) — full storefront + admin cart surface, including the CartLineResponse and CartVendorBag shapes referenced above. See cart.md.
  • catalog — variant ownership is determined by product.vendorId; the vendor scope check uses these joins.
  • ordercheckoutsPrepared events arrive at order place-time; admin analytics shares the same underlying rollup tables.

On this page