> ## Documentation Index
> Fetch the complete documentation index at: https://docs.brand-wallet.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Update a customer

> Update a BrandWallet customer's profile, metadata, consents, or external id.

```http theme={null}
POST $BASE_URL/v1/customers/:id
```

**Updates a customer's `data` (merge), `metaData` (replace), `consents` (merge), and `opaqueId`. Loyalty balances (`collectables`) can never be changed through this endpoint.**

> **Auth:** `Authorization: Bearer <accessToken>` — an organization-scoped token from [Authentication](/api/authentication). Missing or invalid token returns **401**; an organization without an active subscription returns `invalid_subscription` (**403**).

Payload schema:

```json theme={null}
{
  "data": { "fullname": "Jane Smith", "phone": "+905551234567" },  // optional — shallow-merged
  "metaData": { "source": "pos" },                                 // optional — replaced as a whole
  "consents": { "marketing": true | false },                       // optional — shallow-merged
  "opaqueId": "ext-cust-10432"                                     // optional
}
```

### Path parameters

| Parameter | Type | Description |
| - | - | - |
| `id` | string | Customer id. Unknown, deleted, or out-of-organization ids return `customer_not_found` (400). |

### Body

All fields are optional, but at least one must be present — an empty body returns `nothing_to_update` (400).

| Field | Type | Description |
| - | - | - |
| `data` | object | **Shallow-merged** into the existing profile: keys you send are overwritten, keys you omit are kept. `phone` is normalized to E.164 (TR default) — `invalid_phone` (400) when unparseable. `email`/`phone` stay unique within the organization (the customer itself excluded): `existing_customer_with_email` / `existing_customer_with_phone` (400). |
| `metaData` | object | **Replaced as a whole** when sent — sending `{}` deliberately clears it. |
| `consents` | object | **Shallow-merged** — consent flags you omit are kept, so an existing consent record cannot be wiped by accident. Sending `{}` is a no-op. |
| `opaqueId` | string | Your own system's customer id. Unique within the organization — `opaque_id_already_exists` (400) on conflict. |

### Example

```bash theme={null}
curl --request POST \
  --url "$BASE_URL/v1/customers/9b2f6c1e-3d4a-4f8b-9c0d-7e5a1b2c3d4e" \
  --header 'Authorization: Bearer <accessToken>' \
  --header 'Content-Type: application/json' \
  --data '{
    "data": { "fullname": "Jane Smith" },
    "consents": { "marketing": true }
  }'
```

### Response

Returns **200** with the updated customer record.

```json theme={null}
{
  "id": "9b2f6c1e-3d4a-4f8b-9c0d-7e5a1b2c3d4e",
  "data": {
    "phone": "+905551234567",
    "fullname": "Jane Smith"
  },
  "collectables": {
    "stamp_K3M9A": { "value": 4, "total": 12 }
  },
  "consents": { "marketing": true },
  "opaqueId": "ext-cust-10432",
  "status": 1,
  "createdAt": "2026-09-29T08:22:52.795Z",
  "updatedAt": "2026-09-29T10:41:03.917Z"
}
```

Notes:

* The response does not include `cards` — cards are returned by [Register](/api/register-customer) and [Find](/api/find-customer).
* Optional fields that have no value are omitted from the response — you will not receive `null`. Exception: `opaqueId` is `null` when not assigned.
