Orders
Endpoint reference: ✅ Verified live against production on 10 May 2026. Orders are the conversion event in your store — when a customer’s cart becomes a purchase. The Dr Green admin team manually approves each order before it ships, so expect an asynchronous lifecycle: place → pending → admin-verified → paid → shipped.
Order lifecycle (the high-level flow)
Endpoints
Status enums
🔒 The complete enum lists above are inferred from observed values (
PENDING, VERIFIED, REJECTED confirmed live). Other values are likely supported but not yet captured. Defensively handle unknown statuses — render them as the raw string.
POST /dapp/orders — create an order
Creates an order from explicit line items + shipping + (optionally) the cart it came from. The shipping address must be one of the client’s shippings[] (referenced by id).
Request body
The shape followsCreateOrderDto. Verified pattern:
Canonical payload (for signing)
JSON.stringify(body) — compact, no whitespace.
Response
201 Created with the order’s id and invoiceNumber. The invoiceNumber is a 32-char random string Dr Green generates server-side — use it as your customer-facing reference. Don’t generate your own.
Idempotency warning
POST /dapp/orders is not idempotent. A retry creates a duplicate order. Two patterns to avoid this:
Pattern 1 — pre-flight check. Before any retry, query GET /dapp/orders?page=1&limit=20 and look for a recent order matching the customer + total amount. If found, treat your original POST as having succeeded.
Pattern 2 — your-side correlation ID. Generate a UUID at checkout, store order_in_flight: <uuid> in your store DB, only POST once. On any retry, check if the previous attempt eventually returned a 2xx (which you’d have logged) before retrying.
See 04-errors.md § Idempotency for the full discussion.
Errors
GET /dapp/orders — list orders
Paginated list of all orders for the holder.
Query parameters
Response shape (verified)
🪲_countis a Prisma-leaked field — it’s{ orderLines: <count> }. Treat it as informational, not authoritative.
🪲totalAmount,totalPrice, andlocalPrice.totalAmountare different things:
totalAmountandtotalPriceare the same USD figure (mirroring of fields)localPrice.totalAmountis the customer-currency equivalent (e.g. ZAR for South Africa) UselocalPricefor display,totalAmountfor accounting.
GET /dapp/orders/recent — recent orders
Same shape as GET /dapp/orders but always returns the most recent 10 (no pagination needed). Use this as a heartbeat polling target — cheap call that surfaces anything new across all customers.
Canonical payload
{}
GET /dapp/orders/summary — status counts
adminApproval values (not orderStatus). For an orderStatus breakdown over time, use /dapp/orders/status-breakdown with a date range.
GET /dapp/orders/chart-data and /status-breakdown
Both require startDate and endDate in YYYY-MM-DD format. Without them: 400.
Canonical payload (with date range)
startDate=2026-04-01&endDate=2026-05-01
🔒 Response shapes pending live capture with valid date ranges.
GET /dapp/orders/{orderId} — order detail
Full detail including line items with embedded strain info, transactions, and the multi-currency price breakdown.
Canonical payload
{}
Response shape (verified)
⚠️ The order detail wraps the data inside an extraorderDetailsfield — i.e.data.orderDetails, notdatadirectly. Other endpoints don’t wrap like this. Mind the inconsistency.
🪲totalAmountis in USD,localPrice.totalAmountis in the customer’s local currency. Don’t rendertotalAmountto the customer — uselocalPrice.totalAmountwithlocalPrice.currency.
🪲totalOrderedandtotalQuantityare the same number in observed responses. The duplication is a backend wart.
Common patterns
Polling order status
Surface multi-currency totals
Detecting status changes between polls
Caching guidance
See also
- Carts — orders are typically born from carts
- Clients — orders are scoped to clients
- Strains —
orderLines[].strainfields and pricing snapshot - Sales — orders may be associated with a sales pipeline entry
- 06-webhooks.md — why you have to poll