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
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /dapp/orders | API-key + sig | Create a new order from a cart |
GET | /dapp/orders | API-key + sig | List orders, paginated |
GET | /dapp/orders/recent | API-key + sig | Recently-created orders (heartbeat polling target) |
GET | /dapp/orders/summary | API-key + sig | Order status counts |
GET | /dapp/orders/chart-data | API-key + sig | Time-series, requires date range |
GET | /dapp/orders/status-breakdown | API-key + sig | Status breakdown over date range |
GET | /dapp/orders/{orderId} | API-key + sig | Full order detail |
Status enums
| Field | Values | Set by |
|---|---|---|
orderStatus | PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED | Server-side based on lifecycle |
adminApproval | PENDING, VERIFIED, REJECTED | Dr Green admin |
paymentStatus | PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED | Payment processor webhook |
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:
| Field | Type | Required | Notes |
|---|---|---|---|
clientId | string (UUID) | ✅ | Must be adminApproval=VERIFIED, isKYCVerified=true, isActive=true |
shippingId | string (UUID) | ✅ | One of client.shippings[].id |
orderLines | array | ✅ | At least one line |
orderLines[].strainId | string (UUID) | ✅ | |
orderLines[].quantity | integer | ✅ | Grams; positive integer |
paymentMethod | string | ✅ | CRYPTO (CoinRemitter), FIAT (Payinn), PGPAY (PGPay) — confirm with Dr Green which are enabled per country |
cartId | string (UUID) | No | Reference to the cart that fed this order, if any |
notes | string | No | Free-text |
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
| Status | Cause | Fix |
|---|---|---|
400 ["clientId must be a UUID"] | Validation | Validate input |
400 “Client not verified” / similar | Client isn’t VERIFIED + KYC + active | Surface to customer; can’t recover |
400 “Shipping not found for client” | shippingId doesn’t belong to this client | Re-fetch client and use a valid shipping id |
400 “Strain not available in client’s country” | Strain availableCountries doesn’t include client’s country | Filter strains to client’s country before placing order |
GET /dapp/orders — list orders
Paginated list of all orders for the holder.
Query parameters
| Name | Type | Default | Notes |
|---|---|---|---|
page | integer | 1 | |
limit | integer | 10 | |
status | string | — | Filter by orderStatus |
adminApproval | string | — | Filter by adminApproval |
clientId | string (UUID) | — | Filter to one client (or use /dapp/client/{id}/orders) |
search | string | — | Free-text on invoiceNumber |
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
| Resource | TTL | Rationale |
|---|---|---|
| Active order detail | 30s | Customer expects ~real-time feel |
| Terminal-status order detail | 5m | Doesn’t change |
| Order list (paginated) | No cache | Always fresh |
| Order summary counts | 1m | Dashboards |
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