> ## 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

# 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)                                           |

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:

```json theme={null}
{
  "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"
  }
}
```

| 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:

```json theme={null}
{
  "success": true,
  "statusCode": 201,
  "message": "Client created successfully",
  "data": { "id": "<new-uuid>" }
}
```

### 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)

```json theme={null}
{
  "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)

```typescript theme={null}
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

```json theme={null}
{ "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)

```json theme={null}
{
  "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

| 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)

```json theme={null}
{
  "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)

```json theme={null}
{
  "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:

```json theme={null}
{
  "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`](./orders.md), but scoped to one client.

```json theme={null}
{ "data": { "orders": [...], "pageMetaDto": {...} } }
```

***

## `GET /dapp/client/{clientId}/transactions` — client's transactions

> 🪲 Same singular-path warning.

### Response

```json theme={null}
{ "data": { "transactions": [...], "pageMetaDto": {...} } }
```

Shape of each transaction record is not yet captured live. 🔒

***

## Common patterns

### Surfacing approval status to the customer

```ts theme={null}
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](../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

* [Carts](./carts.md) — clients have associated carts via `clientCart[]`
* [Orders](./orders.md) — order placement requires a verified client
* [02-authentication.md](../02-authentication.md) — canonical payload rules
* [04-errors.md](../04-errors.md) — full error envelope reference
