Clients (Customers)
Endpoint reference: ✅ Verified live against production on 10 May 2026.
“Clients” in Dr Green parlance are your store’s end customers. They’re scoped to the holder’s primary NFT — so all clients you create via your store belong to the holder whose apiKey you’re using. KYC, addresses, business details, and medical records all hang off the client record.
Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /dapp/clients | API-key + sig | Create a client (KYC submission) |
GET | /dapp/clients | API-key + sig | List clients, paginated |
GET | /dapp/clients/list | API-key + sig | List clients without pagination wrapper (for selectors) |
GET | /dapp/clients/summary | API-key + sig | Status counts (PENDING / VERIFIED / REJECTED / total) |
GET | /dapp/clients/chart-data | API-key + sig | Time-series data for charts (date-range required) |
GET | /dapp/clients/status-breakdown | API-key + sig | Status breakdown over date range |
GET | /dapp/clients/export | API-key + sig | CSV export |
GET | /dapp/clients/{clientId} | API-key + sig | Full detail for one client |
PATCH | /dapp/clients/{clientId} | API-key + sig | Update client fields |
GET | /dapp/client/{clientId}/orders | API-key + sig | Orders for one client |
GET | /dapp/client/{clientId}/transactions | API-key + sig | Transactions for one client |
GET | /dapp/clients/{clientId}/orders/{orderId} | API-key + sig | One specific order for a client |
🪲 Naming inconsistency in the API: sub-routes are split between/dapp/clients/{clientId}/...and/dapp/client/{clientId}/...(singular vs plural). This is a backend wart — not a typo in this doc. Use whichever path each endpoint expects.
Status enums
Clients have three relevant status fields:| Field | Values | Set by |
|---|---|---|
adminApproval | PENDING, VERIFIED, REJECTED | Dr Green admin team after review |
isKYCVerified | true, false | FirstAML callback (Dr Green ↔ FirstAML; store has no direct involvement) |
isActive | true, false | Holder (soft-disable a client) |
adminApproval: VERIFIED AND isKYCVerified: true AND isActive: true before they can place orders. New clients land in PENDING and require human review.
POST /dapp/clients — create a client
Submit a new client for KYC review. The Dr Green admin team reviews each client manually before they can transact.
Request body
TheCreateClientDto is the input shape. Verified-required and most-common fields:
| Field | Type | Required | Notes |
|---|---|---|---|
firstName | string | ✅ | |
lastName | string | ✅ | |
email | string | ✅ | Validated as RFC 5322; must be unique per holder |
phoneCode | string | ✅ | E.g. "+44", "+1" |
contactNumber | string | ✅ | Digits only, no spaces |
shipping | object | ✅ | Use CreateShippingDto shape, country code in alpha-3 |
medicalRecord | object | string | ⚠️ | Required if customer is purchasing for medical reasons; format depends on country |
clientBusiness | object | No | Use CreateClientBusinessDto if B2B |
Canonical payload (for signing)
JSON.stringify(body) — compact, no whitespace.
Response
201 Created with the new client’s id in data. Detailed shape:
Errors
| Status | Cause | Fix |
|---|---|---|
400 ["email must be a valid email"] | Invalid email format | Validate client-side first |
400 ["countryCode must be a valid ISO 3166-1 alpha-3 country code"] | Sent "GB" instead of "GBR" | Use alpha-3 |
409 Conflict (email duplicate) | Email already exists for this holder | Surface to user, let them log in instead |
GET /dapp/clients — list clients
Paginated list of all clients owned by the current holder.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
page | integer | No (default 1) | |
limit | integer | No (default 10) | |
search | string | No | Free-text on name / email |
status | string | No | Filter by adminApproval (e.g. PENDING) |
kyc | boolean | No | Filter on isKYCVerified |
Canonical payload
urlencode(query) if any params present, else "{}".
Response shape (verified)
🪲phoneCountryCodeandphoneCodeare different fields.phoneCountryCodeis sometimes null whilephoneCode("+91") holds the dial prefix. UsephoneCode + contactNumberfor E.164.
🪲verifiedAtandrejectedAtappear in the list response but not in the detail response. If you need them, capture them at list time.
Worked example (Node)
GET /dapp/clients/list — unpaginated list
Same data as the paginated list but without pageMetaDto and without server-side limit. Use for <select> dropdowns where you need every client at once.
Canonical payload
{} (no query params).
Response
⚠️ Don’t use this for catalogue pages. It returns the full list and gets expensive at scale. Only use for autocomplete / picker UIs.
GET /dapp/clients/summary — status counts
Top-line counts for dashboards.
Canonical payload
{}
Response (verified)
adminApproval values plus totalCount. Useful for a “tabs with counts” UI.
GET /dapp/clients/chart-data and /status-breakdown — time-series
Both endpoints require startDate and endDate query parameters. Without them you get a 400.
Required query parameters
| Name | Format | Notes |
|---|---|---|
startDate | YYYY-MM-DD or MM-DD-YYYY | Inclusive |
endDate | YYYY-MM-DD or MM-DD-YYYY | Inclusive |
Error if omitted (verified)
🪲 The error message has typo’d casing — it says “startdate” lowercase but the actual field is startDate. The validator will accept correctly-cased query params.
Canonical payload
startDate=2026-04-01&endDate=2026-05-01
🔒 Response shape for these is not yet captured live — pending a date-range probe. Update once verified.
GET /dapp/clients/{clientId} — full client detail
The complete record including shipping addresses, businesses, KYC, NFT linkage, and cart references.
Canonical payload
{}
Response shape (verified)
🪲shippingsandclientBusinessesare arrays — a client can have multiple. The order in the array is not guaranteed; sort/select byidorisPrimarywhen implementing.
PATCH /dapp/clients/{clientId} — update a client
Updates a subset of fields on an existing client.
Request body
UseUpdateClientDto. All fields are optional; only the ones you send will be updated:
UpdateShippingDto and UpdateClientBusinessDto flow not yet documented).
Canonical payload
JSON.stringify(body) — compact.
Response
200 OK with data.id of the updated client.
GET /dapp/client/{clientId}/orders — client’s orders
🪲 Note: path is/dapp/client/(singular), not/dapp/clients/. Catch this in your routing.
Canonical payload
{} if no query, else urlencode(query). Supports page and limit.
Response
Same shape asGET /dapp/orders, but scoped to one client.
GET /dapp/client/{clientId}/transactions — client’s transactions
🪲 Same singular-path warning.
Response
Common patterns
Surfacing approval status to the customer
Polling for KYC completion
After a client submits, poll the detail endpoint every 5 minutes untilisKYCVerified === true or adminApproval === 'REJECTED'. FirstAML verification timing varies by case complexity — straightforward cases complete in minutes, more involved AML reviews can take hours. Don’t poll faster than 5 minutes; it adds load without speeding anything up. See guides/kyc-flow.md for the full architecture.
Treating client identifiers
Always use the UUIDid field server-side. Email is not unique across holders (different holders can have customers with the same email). Don’t use email as a primary key in your store DB.
See also
- Carts — clients have associated carts via
clientCart[] - Orders — order placement requires a verified client
- 02-authentication.md — canonical payload rules
- 04-errors.md — full error envelope reference