# Customers

Customers are end-users who placed an order on your storefront. They're created automatically at first checkout — there is no public sign-up flow at v1. Each customer is **scoped to one store**: the same phone number on two different stores creates two `customer` rows.

Deduplication: when a new checkout arrives, we match on store + phone. If that customer already exists, their email/address/wilaya are updated and the record is reused; otherwise a new one is created. **Email is not used for dedup** — historically merchants get many anonymous orders without an email.

When a customer is created through `POST /v1/orders`, the entire `customer.name` string is stored in `first_name` and `last_name` is left empty — the first/last split shown in the examples below never happens for API-created records. For an existing customer matched by phone, only `first_name` is overwritten and any existing `last_name` is left untouched. Split client-side if you need it.

## `GET /v1/customers`

List customers in your store. Cursor-paginated.

**Auth:** platform key with `customers:read`, which new keys carry by default. Without it the call answers `403 forbidden`.

### Query parameters

| Param    | Type      | Notes                                                                                                                                                                                                                                    |
| -------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `limit`  | int 1–200 | Default 50                                                                                                                                                                                                                               |
| `cursor` | string    | Opaque                                                                                                                                                                                                                                   |
| `phone`  | string    | Exact match on the stored number. Storefront and landing page checkouts store Algerian mobiles as `0XXXXXXXXX`; numbers from `POST /v1/orders` and manual dashboard orders are stored as sent, so try the other shape if nothing matches |
| `email`  | string    | Exact match, case-insensitive                                                                                                                                                                                                            |

There are no date, free-text search, sort or `is_banned` filters at v1; results are always newest-id-first.

### Request

```
curl 'https://api.dzbuild.app/v1/customers?limit=20' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Response 200

```
{

  "data": {

    "items": [

      {

        "id":           5578,

        "first_name":   "John",

        "last_name":    "Doe",

        "phone":        "0555000000",

        "email":        null,

        "wilaya_id":    16,

        "commune":      "Bab Ezzouar",

        "total_orders": 3,

        "total_spent":  4500,

        "is_banned":    false,

        "created_at":   "2026-03-17 15:18:13",

        "updated_at":   "2026-04-15 12:01:08"

      }

    ],

    "next_cursor": "NDk=",

    "has_more": true

  }

}
```

`total_orders` / `total_spent` are stale counters, not live aggregates

They are only incremented when an order is placed from a landing page or created manually in the dashboard. They are never adjusted on a status change (a cancelled order still counts), and orders created via the storefront or `POST /v1/orders` never update them at all. Don't use them for revenue reporting — aggregate `GET /v1/orders` (or `/v1/customers/{id}/orders`) yourself.

## `GET /v1/customers/{id}`

Full detail.

**Auth:** platform key with `customers:read`.

### Response 200

```
{

  "data": {

    "id":           5578,

    "first_name":   "John",

    "last_name":    "Doe",

    "phone":        "0555000000",

    "email":        "john@example.com",

    "wilaya_id":    16,

    "commune":      "Bab Ezzouar",

    "address":      "12 Rue X",

    "notes":        null,

    "total_orders": 3,

    "total_spent":  4500,

    "fraud_score":  0,

    "is_banned":    false,

    "created_at":   "2026-03-17 15:18:13",

    "updated_at":   "2026-04-15 12:01:08"

  }

}
```

| Field                     | Notes                                                                                                                                                                                                                                                                              |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `notes`                   | Reserved. Neither the dashboard nor the API writes it, so expect `null` on most customers.                                                                                                                                                                                         |
| `fraud_score`             | **Always `0` at v1** — the field is reserved and never populated through the API. The risk indicator you see on the dashboard Customers page is not exposed here. Do not build fraud logic on this field.                                                                          |
| `is_banned`               | Set when you blacklist the customer in the dashboard. Storefront and landing-page checkouts reject banned customers — but **`POST /v1/orders` does not check the blacklist**, so API-created orders go through for banned customers. Check `is_banned` yourself before submitting. |
| Device / identity signals | **Not exposed via API** for privacy reasons                                                                                                                                                                                                                                        |

## `GET /v1/customers/{id}/orders`

Customer's orders, cursor-paginated, newest first.

**Auth:** platform key with `customers:read`.

The customer id is ownership-checked first, so an unknown id, or one belonging to another store, returns `404 not_found` rather than an empty list.

### Request

```
curl 'https://api.dzbuild.app/v1/customers/5578/orders?limit=10' \

  -H "Authorization: Bearer $DZ_KEY"
```

### Response 200

```
{

  "data": {

    "items": [

      { "id": 6894, "order_number": "ORD-13-20260317-AD3C91F7", "status": "confirmed",

        "payment_status": "pending", "total": 1000, "created_at": "2026-03-17 15:18:13" }

    ],

    "next_cursor": null,

    "has_more": false

  }

}
```

This is a **subset** of `/v1/orders` filtered by `customer_id`, with a shorter summary: `id`, `order_number`, `status`, `payment_status`, `total` and `created_at` only. To get the full order body call `/v1/orders/{id}`.

## Common patterns

### "Find a customer by phone, then list their orders"

```
PHONE="0555000000"

CUST=$(curl -sS "https://api.dzbuild.app/v1/customers?phone=$PHONE&limit=1" \

  -H "Authorization: Bearer $DZ_KEY" | jq -r '.data.items[0].id')



[ -z "$CUST" ] && { echo "no customer"; exit 1; }



curl -sS "https://api.dzbuild.app/v1/customers/$CUST/orders" \

  -H "Authorization: Bearer $DZ_KEY"
```

### "Top 10 spenders this month"

There's no native sort-by-spend filter at v1, and `total_spent` is a stale counter (see the warning above). Query `/v1/orders?since=...` and sum `total` per `customer_phone`: the list summary carries the phone, not the customer id. Skip `cancelled` and `returned` orders if you only want kept sales.

## Privacy notes

* The API never exposes device fingerprints or any cross-store identity link.
* Customer email/phone are PII — be careful about logging them.
* Future right-to-erasure (RGPD-style) requests should go through dashboard / support; the API will gain a `DELETE /v1/customers/{id}` endpoint in a future version with explicit cascade rules.
