Skip to main content
GET /v2/merchant/orders returns the authenticated merchant’s recent OrderV2 rows, JOINed against the Schema C repayment_history_attestations collection for each row’s attestation block, plus a server-computed KPI envelope and pagination. This endpoint is the data source for the merchant Orders V2 page (Designer spec § 9) and the corresponding MCP get_merchant_orders tool (Pillar 4).
Live on dev as of 2026-06-14; PROD ETA after next GTFU. Shipped via PR #2104. Pillar 2 Trust Fabric (Schema C JOIN) × Pillar 4 MCP (server-computed KPIs) dual-purpose surface — keep the read path scoped to the merchant rather than forking a separate MCP-only endpoint.

GET /v2/merchant/orders

Authentication

GuardRequirement
JWTRequired, any authenticated merchant role
ScopeshopId derived from JWT shopId claim — not from query / body
Obtain a merchant JWT via POST /merchant/admin/login — see Authentication. Any ?merchantId= / ?shopId= query parameter is silently dropped (OWASP A01:2021 IDOR mitigation; mirrors MerchantInventoryAttestationsController and MerchantX402EarningsController).

Query parameters

ParamTypeRequiredDefaultDescription
pageintegerNo11-indexed page number.
limitintegerNo20Rows per page. Capped at 400.
startDateISO-8601 stringNoFilter to orders with createdAt >= startDate. Omit for no lower bound.
endDateISO-8601 stringNoFilter to orders with createdAt <= endDate. Omit for no upper bound.
statusenumNoFE bucket filter. One of SETTLED / PENDING / REFUNDED / DISPUTED. Unknown values silently dropped. See Notes for the 8→4 mapping.
pspstringNoFilter by PSP provider (e.g. stripe, paypal, telr). Unknown values silently dropped. Applied in-memory post-Prisma.
windowDaysintegerNo90Trailing window in days when startDate / endDate are omitted. Clamped to 1..365.

Example

curl "https://apiv3.droplinked.com/v2/merchant/orders?page=1&limit=20" \
  -H "Authorization: Bearer <MERCHANT_JWT>"
With filters:
curl "https://apiv3.droplinked.com/v2/merchant/orders?page=1&limit=50&status=SETTLED&psp=stripe&startDate=2026-01-01T00:00:00Z&endDate=2026-06-14T23:59:59Z" \
  -H "Authorization: Bearer <MERCHANT_JWT>"

Response — 200 OK

{
  "rows": [
    {
      "orderId": "6657a1b2c3d4e5f60718293a",
      "orderNumber": "ORD-001",
      "customerEmail": "buyer@example.com",
      "items": 2,
      "grossUsdCents": 5000,
      "netUsdCents": 4500,
      "psp": "stripe",
      "status": "SETTLED",
      "attestation": {
        "uid": "0xUID1",
        "status": "MINTED",
        "rolledUpAt": "2026-06-13T00:00:00.000Z",
        "lenderId": "lender-A",
        "chain": "base",
        "schemaUid": "0xSCHEMAC"
      },
      "settledAt": "2026-06-12T00:00:00.000Z",
      "createdAt": "2026-06-10T00:00:00.000Z"
    }
  ],
  "kpis": {
    "totalOrdersInWindow": 4,
    "settledRevenueUsdCents": 30000,
    "avgOrderValueUsdCents": 15000,
    "attestationCoveragePctBps": 2500
  },
  "pagination": {
    "page": 1,
    "limit": 20,
    "totalPages": 1,
    "totalCount": 4
  },
  "windowDays": 90,
  "asOf": "2026-06-14T17:40:00.000Z"
}

Fields

FieldTypeNullableDescription
rowsarrayNoOne entry per OrderV2 row matching the filters.
rows[].orderIdstringNoOrderV2 _id.
rows[].orderNumberstringNoHuman-readable order number (e.g. ORD-001).
rows[].customerEmailstringYesBuyer email. null for guest orders that captured no email.
rows[].itemsintegerNoTotal item count across the order’s line items.
rows[].grossUsdCentsintegerNoGross order amount in USD cents, integer.
rows[].netUsdCentsintegerNoNet to the merchant after platform fees + PSP fees, USD cents.
rows[].pspstringNoPSP provider that authorized the transaction (e.g. stripe, paypal, telr, bonum, paymob).
rows[].statusenumNoFE bucket: SETTLED / PENDING / REFUNDED / DISPUTED. See Notes for the 8→4 collapse.
rows[].attestationobjectNoPer-row Schema C attestation block. Always present — empty / pending blocks fill the missing-coverage case (see Schema C JOIN section).
rows[].attestation.uidstringYesEAS attestation UID. null when no attestation exists for this order.
rows[].attestation.statusenumNoMINTED / PENDING / FAILED / NOT_APPLICABLE.
rows[].attestation.rolledUpAtISO-8601 stringYesWhen the attestation was rolled into the on-chain registry. null for PENDING / NOT_APPLICABLE.
rows[].attestation.lenderIdstringYesLenderRegistry id this attestation rolls up to. null for NOT_APPLICABLE.
rows[].attestation.chainstringYesChain the UID is on (base for Schema C on Base mainnet). null for NOT_APPLICABLE.
rows[].attestation.schemaUidstringYesSchema UID — Schema C (0x090a...f24c) for repayment_history_attestations. null for NOT_APPLICABLE.
rows[].settledAtISO-8601 stringYesSettlement timestamp (UTC). null for non-settled buckets.
rows[].createdAtISO-8601 stringNoOrder-creation timestamp (UTC).
kpisobjectNoWindow-aggregate envelope, computed server-side.
kpis.totalOrdersInWindowintegerNoCount of orders across the entire window (not just the current page).
kpis.settledRevenueUsdCentsintegerNoSum of grossUsdCents for SETTLED rows across the entire window. USD cents.
kpis.avgOrderValueUsdCentsintegerNosettledRevenueUsdCents / count(SETTLED rows), integer cents. 0 when no SETTLED rows.
kpis.attestationCoveragePctBpsintegerNoSchema C attestation coverage as basis points (0..10000). 2500 = 25.00%. Integer wire so no float drift. See KPI envelope section.
paginationobjectNoPagination envelope.
pagination.pageintegerNoEchoed page query param.
pagination.limitintegerNoEffective limit (after the 400-cap).
pagination.totalPagesintegerNoceil(totalCount / limit).
pagination.totalCountintegerNoTotal order rows matching the filter (across pages). Equal to kpis.totalOrdersInWindow.
windowDaysintegerNoThe effective window size in days (post-clamp).
asOfISO-8601 stringNoSnapshot timestamp (server clock, UTC).

Errors

StatusBodyWhen
400{ "statusCode": 400, "status": "failed", "data": { "message": "startDate must be a valid ISO-8601 date" } }startDate / endDate fail ISO-8601 parsing. Unknown enum values for status / psp are silently dropped, not rejected.
401{ "statusCode": 401, "status": "failed", "data": { "message": "Unauthorized" } }Missing or invalid JWT.
5xx(does not occur for read-path failures — see fail-open section)The endpoint never returns 5xx for downstream read-path failures; it degrades per-section. Real 5xx would be a process-level fault.

Schema C JOIN

Each rows[].attestation block comes from a JOIN against the Schema C repayment_history_attestations collection — the on-chain rollup of order repayment history that backs the Trust Fabric. Implementation details:
  • Single bulk query. The JOIN is a single $in query against the sparse orderId index on repayment_history_attestations. No N+1; the service collects every orderId from the Prisma fetch first, then issues one Schema C read for the page.
  • Newest-wins. If multiple Schema C rows reference the same orderId (re-mint flows), the most recently rolledUpAt row is returned. The collection retains history; the JOIN reads the latest.
  • Schema UID. Every returned block carries the Schema C UID (0xSCHEMAC placeholder above; the production value is registered on Base mainnet — see the EAS Schema v2 shipped 2026-06-11 note in the trust-fabric concept page). Clients can use this to disambiguate from Schemas A / B / D in a future mixed-schema view.
  • Missing coverage. Orders with no Schema C row land attestation.status: "NOT_APPLICABLE" (orders that don’t qualify, e.g. refunded-before-settlement) or "PENDING" (qualifying orders not yet rolled up). Rows are never dropped from rows[] because of a missing attestation — coverage is signaled via the per-row status field and aggregated into kpis.attestationCoveragePctBps.

KPI envelope

The KPI envelope is computed server-side so the FE never recomputes totals from the visible page (which would understate the window). All amounts are USD cents to keep aggregation float-safe; attestationCoveragePctBps is basis points (0..10000) for the same reason — 25.00% ships as 2500, no 0.25 floats on the wire.
  • totalOrdersInWindow — order rows matching the filter across the full window (not just the visible page).
  • settledRevenueUsdCentsΣ grossUsdCents for rows whose FE bucket is SETTLED. Refunded / disputed rows are excluded.
  • avgOrderValueUsdCentssettledRevenueUsdCents / count(SETTLED rows), integer division. 0 when no SETTLED rows in window.
  • attestationCoveragePctBpsfloor(10000 × count(attestation.status === "MINTED") / totalOrdersInWindow). Clamped to 0..10000. 0 when the window is empty.

Per-section fail-open

The endpoint follows the Stripe Reliability + Shopify Platform fail-open discipline established by PR #1975 + #2042 — partial failures degrade per-section, never propagate as 5xx:
SectionHealthyFailure mode
Prisma OrderV2 readpopulated rows[]empty rows: [], empty kpis envelope, 200 OK. Sentry-tagged.
Schema C JOINpopulated attestation blocksrows[] is kept with attestation.status: "PENDING" / "NOT_APPLICABLE". Rows are never dropped due to JOIN failure — the merchant must still see their own orders. Sentry-tagged.
KPI computepopulated kpisrows kept, kpis returns the zero-envelope EMPTY_KPIS shape (totalOrdersInWindow: 0, settledRevenueUsdCents: 0, avgOrderValueUsdCents: 0, attestationCoveragePctBps: 0). Sentry-tagged.
The merchant never sees a partial-Orders-page error in the dashboard.

Notes

  • 8→4 status mapping. The Prisma OrderStatus enum has 8 values (CREATED, CONFIRMED, PROCESSING, SHIPPED, DELIVERED, REFUNDED, CANCELLED, DISPUTED); the FE bucket has 4 (SETTLED / PENDING / REFUNDED / DISPUTED). Mapping is centralized in MerchantOrdersService.mapStatusBucket():
    • DELIVERED + SHIPPED + PROCESSING + CONFIRMEDSETTLED
    • REFUNDED + CANCELLEDREFUNDED
    • DISPUTEDDISPUTED
    • CREATED and unmapped → PENDING (excluded from SETTLED revenue; included in totalOrdersInWindow).
  • Caching. The response carries Cache-Control: private, max-age=60. Clients may see up to a 60s lag for fresh order events through CDN caches.
  • PSP filter is in-memory. Prisma’s Mongo provider does not efficiently filter on a composite-type payment.provider field, so the PSP filter is applied in-memory after the Prisma fetch. Pagination math is over the filtered set.
  • Auth scope. shopId from req.user.shopId only. Any ?merchantId= / ?shopId= query parameter is silently dropped. A merchant cannot read another merchant’s orders through this endpoint.
  • Currency. All monetary fields are USD cents, integer — same contract as x402 Earnings (Merchant). Divide by 100 for major-unit display.
  • MCP twin. The same data feeds the get_merchant_orders MCP tool (Pillar 4). The wire shape is identical so a single OpenAPI schema generates both clients.