Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.dr.green/llms.txt

Use this file to discover all available pages before exploring further.

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

MethodPathAuthDescription
POST/dapp/clientsAPI-key + sigCreate a client (KYC submission)
GET/dapp/clientsAPI-key + sigList clients, paginated
GET/dapp/clients/listAPI-key + sigList clients without pagination wrapper (for selectors)
GET/dapp/clients/summaryAPI-key + sigStatus counts (PENDING / VERIFIED / REJECTED / total)
GET/dapp/clients/chart-dataAPI-key + sigTime-series data for charts (date-range required)
GET/dapp/clients/status-breakdownAPI-key + sigStatus breakdown over date range
GET/dapp/clients/exportAPI-key + sigCSV export
GET/dapp/clients/{clientId}API-key + sigFull detail for one client
PATCH/dapp/clients/{clientId}API-key + sigUpdate client fields
GET/dapp/client/{clientId}/ordersAPI-key + sigOrders for one client
GET/dapp/client/{clientId}/transactionsAPI-key + sigTransactions for one client
GET/dapp/clients/{clientId}/orders/{orderId}API-key + sigOne 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:
FieldValuesSet by
adminApprovalPENDING, VERIFIED, REJECTEDDr Green admin team after review
isKYCVerifiedtrue, falseFirstAML callback (Dr Green ↔ FirstAML; store has no direct involvement)
isActivetrue, falseHolder (soft-disable a client)
A client must be 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

The CreateClientDto is the input shape. Verified-required and most-common fields:
{
  "firstName": "Ava",
  "lastName": "Nguyen",
  "email": "ava@example.com",
  "phoneCode": "+44",
  "contactNumber": "7700900123",
  "shipping": {
    "address1": "12 Baker Street",
    "address2": "",
    "city": "London",
    "state": "England",
    "country": "United Kingdom",
    "countryCode": "GBR",
    "postCode": "NW1 6XE"
  }
}
FieldTypeRequiredNotes
firstNamestring
lastNamestring
emailstringValidated as RFC 5322; must be unique per holder
phoneCodestringE.g. "+44", "+1"
contactNumberstringDigits only, no spaces
shippingobjectUse CreateShippingDto shape, country code in alpha-3
medicalRecordobject | string⚠️Required if customer is purchasing for medical reasons; format depends on country
clientBusinessobjectNoUse 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:
{
  "success": true,
  "statusCode": 201,
  "message": "Client created successfully",
  "data": { "id": "<new-uuid>" }
}

Errors

StatusCauseFix
400 ["email must be a valid email"]Invalid email formatValidate 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 holderSurface to user, let them log in instead

GET /dapp/clients — list clients

Paginated list of all clients owned by the current holder.

Query parameters

NameTypeRequiredNotes
pageintegerNo (default 1)
limitintegerNo (default 10)
searchstringNoFree-text on name / email
statusstringNoFilter by adminApproval (e.g. PENDING)
kycbooleanNoFilter on isKYCVerified

Canonical payload

urlencode(query) if any params present, else "{}".

Response shape (verified)

{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": {
    "clients": [
      {
        "id": "2cbab026-9dc6-46fd-8580-77e3dfd2f086",
        "firstName": "Dheeraj",
        "lastName": "Kumar",
        "email": "dheeraj.k+11@rejolut.com",
        "phoneCountryCode": null,
        "phoneCode": "+91",
        "contactNumber": "6394234342",
        "isActive": true,
        "adminApproval": "VERIFIED",
        "isKYCVerified": false,
        "verifiedAt": "2025-05-14T09:28:04.804Z",
        "rejectedAt": null,
        "nft": { "tokenId": 56, "ownerId": "0x553374bd6e81f952..." },
        "shippings": [ { "country": "United States", "currency": null } ],
        "createdAt": "2025-02-26T05:49:11.465Z",
        "updatedAt": "2025-05-14T09:28:04.804Z",
        "clientCart": [ { "id": "0a44cc00-3f2c-45b3-a09d-919ba4d8f5bb" } ]
      }
    ],
    "pageMetaDto": {
      "page": "1", "take": 10, "itemCount": 19,
      "pageCount": 2, "hasPreviousPage": false, "hasNextPage": true
    }
  }
}
🪲 phoneCountryCode and phoneCode are different fields. phoneCountryCode is sometimes null while phoneCode ("+91") holds the dial prefix. Use phoneCode + contactNumber for E.164.
🪲 verifiedAt and rejectedAt appear in the list response but not in the detail response. If you need them, capture them at list time.

Worked example (Node)

async function listClients(page = 1, limit = 10) {
  const query = { page: String(page), limit: String(limit) };
  const payload = canonicalPayload('GET', null, query);
  const headers = authHeaders(API_KEY, SECRET_KEY, payload);
  const url = new URL('https://api.drgreennft.com/api/v1/dapp/clients');
  Object.entries(query).forEach(([k, v]) => url.searchParams.set(k, v));
  const res = await fetch(url, { headers });
  return (await res.json()).data; // { clients, pageMetaDto }
}

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

{ "data": { "clients": [ /* all clients */ ] } }
⚠️ 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)

{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": {
    "summary": {
      "PENDING": 11,
      "VERIFIED": 7,
      "REJECTED": 1,
      "totalCount": 19
    }
  }
}
The keys are the 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

NameFormatNotes
startDateYYYY-MM-DD or MM-DD-YYYYInclusive
endDateYYYY-MM-DD or MM-DD-YYYYInclusive

Error if omitted (verified)

{
  "statusCode": 400,
  "message": "startdate must be a valid date. ex: YYYY-MM-DD or MM-DD-YYYY, startDate should not be empty, endDate must be a valid date. ex: YYYY-MM-DD or MM-DD-YYYY, endDate should not be empty"
}
🪲 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)

{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": {
    "id": "2cbab026-9dc6-46fd-8580-77e3dfd2f086",
    "firstName": "Dheeraj",
    "lastName": "Kumar",
    "email": "dheeraj.k+11@rejolut.com",
    "phoneCountryCode": null,
    "phoneCode": "+91",
    "contactNumber": "6394234342",
    "isActive": true,
    "nft": { "tokenId": 56, "ownerId": "0x553374bd6e81f952..." },
    "adminApproval": "VERIFIED",
    "isKYCVerified": false,
    "createdAt": "2025-02-26T05:49:11.465Z",
    "updatedAt": "2025-05-14T09:28:04.804Z",
    "clientCart": [ { "id": "0a44cc00-3f2c-45b3-a09d-919ba4d8f5bb" } ],
    "shippings": [
      {
        "id": "167d30ee-52d1-48eb-b501-b1203ad72074",
        "address1": "F-203 Test address",
        "address2": "",
        "landmark": "",
        "city": "...",
        "state": "...",
        "country": "...",
        "countryCode": "USA",
        "postCode": "..."
      }
    ],
    "clientBusinesses": [
      {
        "id": "7f7c1ec9-2006-4593-acfc-903ec76e64a8",
        "name": "",
        "businessType": "",
        "address1": "", "address2": "", "landmark": ""
      }
    ],
    "medicalRecord": null
  }
}
🪲 shippings and clientBusinesses are arrays — a client can have multiple. The order in the array is not guaranteed; sort/select by id or isPrimary when implementing.

PATCH /dapp/clients/{clientId} — update a client

Updates a subset of fields on an existing client.

Request body

Use UpdateClientDto. All fields are optional; only the ones you send will be updated:
{
  "firstName": "Ava",
  "isActive": true
}
To update shipping or business records, use the dedicated endpoints (TODO — 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 as GET /dapp/orders, but scoped to one client.
{ "data": { "orders": [...], "pageMetaDto": {...} } }

GET /dapp/client/{clientId}/transactions — client’s transactions

🪲 Same singular-path warning.

Response

{ "data": { "transactions": [...], "pageMetaDto": {...} } }
Shape of each transaction record is not yet captured live. 🔒

Common patterns

Surfacing approval status to the customer

function readableStatus(c: Client): string {
  if (!c.isActive) return 'Account disabled';
  if (c.adminApproval === 'REJECTED') return 'Application rejected';
  if (c.adminApproval === 'PENDING') return 'Pending review by Dr Green';
  if (!c.isKYCVerified) return 'KYC verification required';
  return 'Verified — can place orders';
}

Polling for KYC completion

After a client submits, poll the detail endpoint every 5 minutes until isKYCVerified === 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 UUID id 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