Supercommerce API Docs
Admin API

Order Module — Admin

HTTP surface for platform-admin oversight of orders, returns, and vendor payouts. Read every order on the platform; perform ops actions (cancel on behalf of the customer, manually…

HTTP surface for platform-admin oversight of orders, returns, and vendor payouts. Read every order on the platform; perform ops actions (cancel on behalf of the customer, manually mark a stuck pending payment as paid, mark a paid order as refunded, force-create or override returns); browse vendor ledgers and disburse payouts.

Source: api-modules/order/src/controllers/admin-orders.controller.ts, api-modules/order/src/controllers/admin-returns.controller.ts, api-modules/order/src/controllers/admin-payouts.controller.ts.

Orders are split per-vendor — a parent order row aggregates the customer-facing totals while each order_vendor row is the fulfilment unit. Inventory reservations, fulfilment status, and the vendor ledger all key off order_vendor.


Conventions

Authentication

All endpoints require a Better-Auth admin session and a role granting the matching permission.

Endpoint groupPermission
GET /admin/orders, GET /admin/orders/:id, GET /admin/orders/:id/eventsorder: view
POST /admin/orders/:id/cancel, POST /admin/orders/cleanup-stale-pendingorder: cancel
POST /admin/orders/:id/mark-paid, POST /admin/orders/:id/mark-refundedorder: update
GET /admin/returns, GET /admin/returns/:idorder: view
POST /admin/orders/:id/returns, POST /admin/returns/:id/overrideorder: update
GET /admin/vendors/:id/balance, GET /admin/vendors/:id/ledger, GET /admin/vendors/:id/payouts, GET /admin/payouts, GET /admin/payouts/:idpayout: view
POST /admin/vendors/:id/payouts, POST /admin/payouts/promotepayout: create
POST /admin/payouts/:id/mark-paidpayout: mark_paid
POST /admin/payouts/:id/cancelpayout: cancel
POST /admin/vendors/:id/ledger/adjustpayout: adjust

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
401UNAUTHORIZED
403FORBIDDEN
404NOT_FOUND
409CONFLICT (illegal state transition, e.g. cancelling a delivered order)
500INTERNAL_SERVER_ERROR, DATABASE_ERROR

Money fields

Every amount field is an integer subunit (paise / cents). commissionRate is in basis points (10000 = 100.00%).


Domain types

OrderResponse

type OrderResponse = {
  id: string;
  orderNumber: string;
  status: OrderStatus;
  paymentStatus: PaymentStatus;
  paymentProvider: string;
  paymentMethod: string;
  platform: Platform;

  shippingAddress: AddressBlock;
  billingAddress: AddressBlock;

  subtotal: number;                  // subunits
  discountTotal: number;
  shippingTotal: number;
  taxTotal: number;
  grandTotal: number;

  vendorBreakdowns: OrderVendorResponse[];   // per-vendor sub-orders + lines
  events: OrderEventResponse[];              // recent audit tail (up to 50)

  pendingClientAction: {
    provider: string;
    payload: Record<string, unknown>;
  } | null;

  placedAt: string;
  confirmedAt: string | null;
  paidAt: string | null;
  cancelledAt: string | null;
  cancellationReason: string | null;
};

OrderEventResponse

type OrderEventResponse = {
  id: string;
  orderVendorId: string | null;
  eventType: string;                 // e.g. "payment.captured", "order_vendor.delivered", "return.requested"
  actorType: "customer" | "vendor" | "admin" | "system";
  actorId: string | null;
  source: string;                    // free-form, e.g. "admin-panel", "webhook"
  changes: Record<string, unknown>;
  metadata: Record<string, unknown>;
  createdAt: string;                 // ISO
};

ReturnResponse

type ReturnResponse = {
  id: string;
  returnNumber: string;
  orderId: string;
  orderVendorId: string;
  customerId: string | null;
  vendorId: string;
  type: string;                      // service-defined ("refund", "exchange")
  status: string;                    // "requested" | "approved" | "rejected" | "picked_up" | "received" | "qc_passed" | "qc_failed" | "refunded" | "cancelled"
  reasonCode: string | null;
  reasonNotes: string | null;
  refundAmount: number;              // subunits
  refundedAmount: number;
  externalRefundReference: string | null;
  shippingProvider: string | null;
  awbNumber: string | null;
  trackingCode: string | null;
  rejectionReason: string | null;
  qcFailureReason: string | null;
  requestedAt: string;
  approvedAt: string | null;
  rejectedAt: string | null;
  pickedUpAt: string | null;
  receivedAt: string | null;
  qcPassedAt: string | null;
  qcFailedAt: string | null;
  refundedAt: string | null;
  cancelledAt: string | null;
  lines: ReturnLineResponse[];
  photos: ReturnPhotoResponse[];
};

PayoutResponse

type PayoutResponse = {
  id: string;
  payoutNumber: string;
  vendorId: string;
  status: "pending" | "paid" | "cancelled" | "failed";
  periodStart: string;
  periodEnd: string;
  grossTotal: number;
  commissionTotal: number;
  netTotal: number;
  entryCount: number;
  bankAccountId: string | null;
  bankReference: string | null;
  notes: string | null;
  createdAt: string;
  paidAt: string | null;
  cancelledAt: string | null;
  entries?: LedgerEntryResponse[];   // only populated on the detail endpoint
};

LedgerEntryResponse

type LedgerEntryResponse = {
  id: string;
  vendorId: string;
  kind: "sale" | "refund" | "manual" | "commission_adjustment";
  status: "pending" | "available" | "paid_out" | "cancelled";
  grossAmount: number;
  commissionRate: number;            // basis points
  commissionAmount: number;
  netAmount: number;
  orderId: string | null;
  orderVendorId: string | null;
  orderReturnId: string | null;
  payoutId: string | null;
  pendingUntil: string | null;
  availableAt: string | null;
  paidOutAt: string | null;
  cancelledAt: string | null;
  description: string | null;
  createdAt: string;
};

VendorBalanceResponse

type VendorBalanceResponse = {
  vendorId: string;
  pending: number;                   // net subunits in 'pending' status
  available: number;                 // net subunits in 'available' status not yet on a draft payout
  lifetimeEarned: number;
  lifetimeRefunded: number;
  lifetimePaidOut: number;
  payoutHold: boolean;               // vendor.payouts.payout_hold flag
  commissionRate: number;            // basis points
};

Orders

Base path: /admin/orders.

GET /admin/orders — List orders

Required permission: order: view. Newest first across every customer and vendor.

Query

NameTypeDefaultNotes
pageint1>= 1
limitint(paginated default)
statusOrderStatus?Parent-order lifecycle filter

Response 200 — paginated envelope of OrderResponse[].


GET /admin/orders/:id — Order detail

Required permission: order: view. Admin sees all fields (including provider payload). Inlines the most recent ~50 events; use /events for the full audit trail.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown id

POST /admin/orders/:id/cancel — Cancel on behalf of customer

Required permission: order: cancel. Allowed only when no sub-order has been delivered. Cascades sub-orders, releases per-vendor reservations, writes audit rows.

Body

{ "reason": "Customer asked via support chat" }
FieldTypeConstraints
reasonstring?Trimmed, 1..500

Response 200 — cancelled OrderResponse.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown id
409CONFLICTA sub-order is already delivered

POST /admin/orders/:id/mark-paid — Manually mark paid

Required permission: order: update. For bank-transfer settlements, COD edge cases (vendor confirmed delivery offline), or support overrides. Rejects orders already paid or cancelled. If the order was at pending_payment, transitions it to confirmed and commits the inventory reservation.

Body

{
  "externalReference": "NEFT-UTR-12345",      // optional, 1..200
  "reason": "Customer wired funds directly"   // optional, 1..500
}

Response 200 — updated OrderResponse.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown id
409CONFLICTAlready paid or cancelled

POST /admin/orders/:id/mark-refunded — Mark refunded

Required permission: order: update. v1 refund flow is admin-driven — the admin issues the refund out-of-band in the gateway's dashboard, then calls this to flip payment_status to refunded. Order lifecycle status (confirmed/delivered) stays as-is — refund is a money-only operation.

Body

{
  "amount": 100000,                            // optional; defaults to remaining unpaid amount
  "returnId": "01J9...",                       // optional; links event to an order_return row
  "externalReference": "RZP-RFND-abc",         // optional, 1..200
  "reason": "Goodwill credit for late delivery"
}
FieldTypeConstraints
amountint?>= 1 subunits. Defaults to the remaining unpaid amount
returnIdstring?1..100; links per-return refund counter
externalReferencestring?1..200
reasonstring?1..500

Response 200 — refunded OrderResponse.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown id
409CONFLICTOrder not in a refundable state

GET /admin/orders/:id/events — Paginated audit log

Required permission: order: view. Order detail inlines only the most recent ~50 events; this endpoint pages through the full history.

Query

NameTypeDefaultNotes
pageint1>= 1
limitint
eventTypestring?Trimmed, 1..128. Narrow to one event class

Response 200 — paginated envelope of OrderEventResponse[].

Errors

StatusCodeWhen
404NOT_FOUNDUnknown order id

POST /admin/orders/cleanup-stale-pending — Stale pending-payment sweep

Required permission: order: cancel. Out-of-band escape hatch matching the hourly BullMQ cron. Cancels orders stuck in pending_payment longer than timeoutHours (defaults to admin.payment.pending_timeout_hours). Caps blast radius at limit orders per call.

Body

{ "timeoutHours": 24, "limit": 200 }
FieldTypeConstraints
timeoutHoursint?0..720. Defaults to admin.payment.pending_timeout_hours
limitint?1..1000. Defaults to the constant PENDING_PAYMENT_SWEEP_BATCH

Response 200

{ "data": { "scanned": 312, "cancelled": 14, "errors": 0 }, "message": "Success", "statusCode": 200 }

Returns

GET /admin/returns — List returns

Required permission: order: view. Cross-vendor list.

Query

NameTypeDefaultNotes
pageint1
limitint
statusstring?Trimmed, 1..32. Filter by return status

Response 200 — paginated envelope of ReturnResponse[].


GET /admin/returns/:id — Return detail

Required permission: order: view.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown id

POST /admin/orders/:id/returns — Force-create a return

Required permission: order: update. Support / dispute-resolution path: admin creates a return without the customer's own request. Optionally pre-approves the return and overrides the computed refund_amount.

Body

{
  "orderVendorId": "01J9...",
  "reasonCode": "damaged_in_transit",
  "reasonNotes": "Customer photos in support ticket #ABC-123",
  "lines": [
    { "orderLineId": "01J9...", "quantity": 1, "reasonCode": "damaged_in_transit" }
  ],
  "preApprove": true,
  "refundAmountOverride": 90000
}
FieldTypeConstraints
orderVendorIdstringRequired; the sub-order being returned
reasonCodestring1..64
reasonNotesstring?max 2000
lines[]array1..100 entries
preApproveboolean?Skip vendor approval step
refundAmountOverrideint?>= 0 subunits

Response 200 — created ReturnResponse.

Errors

StatusCodeWhen
400VALIDATION_ERRORBody fails zod
404NOT_FOUNDOrder or sub-order not found

POST /admin/returns/:id/override — Admin override transition

Required permission: order: update. Escape hatch transitions on a wedged return.

actionEffect
"force_refund"Clears qc_failure and moves to qc_passed so /admin/orders/:id/mark-refunded accepts the returnId
"approve"Reverses a vendor rejection — back to approved
"cancel"Cancels the return

Body

{ "action": "force_refund", "reason": "QC dispute escalation; refunding as goodwill" }
FieldTypeConstraints
actionenumOne of force_refund / approve / cancel
reasonstringRequired; 1..500

Response 200 — overridden ReturnResponse.

Errors

StatusCodeWhen
400VALIDATION_ERRORBody fails zod
404NOT_FOUNDUnknown id

Vendor payouts

GET /admin/vendors/:id/balance — Vendor balance summary

Required permission: payout: view.

Response 200VendorBalanceResponse.


GET /admin/vendors/:id/ledger — Vendor ledger

Required permission: payout: view.

Query

NameTypeDefaultNotes
pageint1
limitint
kind"sale" | "refund" | "manual" | "commission_adjustment"?
status"pending" | "available" | "paid_out" | "cancelled"?

Response 200 — paginated envelope of LedgerEntryResponse[].


GET /admin/vendors/:id/payouts — Vendor payouts

Required permission: payout: view.

Query

NameTypeDefaultNotes
pageint1
limitint
status"pending" | "paid" | "cancelled" | "failed"?

Response 200 — paginated envelope of PayoutResponse[] (without entries).


GET /admin/payouts — Cross-vendor payout list

Required permission: payout: view. Same query as the per-vendor list.

Response 200 — paginated envelope of PayoutResponse[] (without entries).


GET /admin/payouts/:id — Payout detail

Required permission: payout: view. Returns the payout with its linked ledger entries inlined.

Response 200PayoutResponse with entries[] populated.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown id

POST /admin/vendors/:id/payouts — Create a draft payout

Required permission: payout: create. Picks all unattached available ledger entries (optionally within the supplied period), snapshots the sums, and creates a pending payout row. Rejects when the vendor is on hold (vendor.payouts.payout_hold = true) or when no available entries exist.

Body

{
  "periodStart": "2026-04-01T00:00:00.000Z",
  "periodEnd":   "2026-04-30T23:59:59.999Z",
  "notes": "April settlement"
}
FieldTypeNotes
periodStartISO datetime?Inclusive lower bound. Default: no lower bound
periodEndISO datetime?Inclusive upper bound. Default: now
notesstring?Trimmed, max 2000

Response 201 — draft PayoutResponse.

Errors

StatusCodeWhen
403FORBIDDENVendor on payout hold
400VALIDATION_ERRORNo available entries match the period

POST /admin/payouts/:id/mark-paid — Mark a draft as paid

Required permission: payout: mark_paid. Admin pastes the bank reference (NEFT UTR, IMPS ref, etc.) from the offline transfer. Flips the payout to paid and every linked ledger entry to paid_out.

Body

{ "bankReference": "NEFT-UTR-12345", "notes": "Sent via HDFC NEFT" }
FieldTypeConstraints
bankReferencestringRequired, trimmed, 1..200
notesstring?Trimmed, max 2000

Response 200 — paid PayoutResponse.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown id
409CONFLICTPayout not in pending

POST /admin/payouts/:id/cancel — Cancel a draft

Required permission: payout: cancel. Releases linked ledger entries back to the pool (available).

Body

{ "reason": "Wrong period selected; redrafting" }
FieldTypeConstraints
reasonstringRequired, trimmed, 1..500

Response 200 — cancelled PayoutResponse.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown id

POST /admin/vendors/:id/ledger/adjust — Manual ledger adjustment

Required permission: payout: adjust. Use for chargebacks, goodwill credits, off-platform reconciliation. Lands as a manual or commission_adjustment entry in available status — picked up by the next payout.

Body

{
  "amount": 50000,                    // signed subunits; positive credits, negative debits
  "kind": "manual",                   // or "commission_adjustment"
  "description": "Q1 goodwill credit"
}
FieldTypeConstraints
amountsigned intNo min — manual adjustments bypass commission math
kindenum"manual" or "commission_adjustment"
descriptionstringRequired, trimmed, 1..500

Response 200 — updated VendorBalanceResponse.


POST /admin/payouts/promote — Manually promote pending → available

Required permission: payout: create. Ops escape hatch when the BullMQ cron has not run or has been disabled. Runs PayoutService.promotePendingEntries() once.

Response 200

{ "data": { "promoted": 42 }, "message": "Success", "statusCode": 200 }

  • admin-rbac — gates every endpoint via order:* and payout:*. See admin-rbac.md.
  • cart — converted carts produce orders. See cart.md.
  • inventory — order placement reserves; mark-paid commits; cancel releases.
  • payment — emits payment.captured events that drive the auto mark-paid path (manual override goes through this admin endpoint).
  • shipping — vendors transition sub-orders through fulfilment states.
  • vendorvendor.payouts.payout_hold setting gates POST /admin/vendors/:id/payouts.
  • settingsadmin.payment.pending_timeout_hours drives the stale-pending sweep.
  • notifications — order events emit notification triggers; see notifications.md.

On this page