# Tham chiếu: Khách hàng

Khách hàng, địa chỉ và ghi chú. Mọi đường dẫn dưới `https://danix.vn/api/open/v1`. Đặc tả máy đọc: [openapi.json](/openapi.json).

## Liệt kê khách hàng {#listCustomers}

`GET /customers`

Phân trang theo con trỏ. Lọc theo số điện thoại bằng `phone`.

**Quyền cần có:** khoá có quyền `pos.customers.read`.

**Tham số query**

| Tên | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `limit` | integer | không | Số dòng mỗi trang, từ 1 đến 100. Mặc định 50. |
| `cursor` | string | không | Con trỏ lấy từ `nextCursor` của trang trước, để lật trang trong CÙNG một lượt đọc (giữ nguyên các tham số khác). Không tự dựng, không lưu để nối lượt đồng bộ sau. |
| `updatedSince` | string (date-time) | không | Chỉ lấy bản ghi có `updatedAt` từ thời điểm này (ISO 8601 UTC), tính cả mốc. Đây là cách đồng bộ tăng dần: có tham số này thì danh sách chỉ trả bản ghi đã qua khoảng trễ an toàn (trần thời gian tua lại kho của shop cộng 15 giây, mặc định 2 phút 15 giây). |
| `includeDeleted` | "true" \| "false" | không | `true` để lấy cả bản ghi đã xoá (có `deletedAt`). Mặc định `false`. |
| `phone` | string | không | Tìm đúng số điện thoại (chấp nhận 0xxxxxxxxx hoặc +84…). |
| `search` | string | không | Tìm theo tên. |

**Ví dụ yêu cầu**

```bash
curl -X GET "https://danix.vn/api/open/v1/customers" \
  -H "Authorization: Bearer dnx_live_…"
```

**Phản hồi mẫu (200)**

```json
{
  "data": [
    {
      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
      "name": "Nguyễn Văn An",
      "gender": "male",
      "dateOfBirth": null,
      "source": "order",
      "isBlocked": false,
      "primaryPhone": "0901234567",
      "primaryEmail": null,
      "createdAt": "2026-10-02T03:15:00.000Z",
      "updatedAt": "2026-10-02T03:15:00.000Z",
      "deletedAt": null
    }
  ],
  "nextCursor": null
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `data` | array<object> | có | Các bản ghi của trang này. |
| `data[].id` | string (uuid) | có | Mã khách hàng. |
| `data[].name` | string | có | Tên khách. |
| `data[].gender` | "male" \| "female" \| "other" hoặc null | có | Giới tính. |
| `data[].dateOfBirth` | string hoặc null | có | Ngày sinh, dạng YYYY-MM-DD. |
| `data[].source` | "manual" \| "order" \| "import" | có | Nguồn tạo hồ sơ: `manual`, `order` hoặc `import`. |
| `data[].isBlocked` | boolean | có | Khách đang bị chặn. |
| `data[].primaryPhone` | string hoặc null | có | Số điện thoại chính. |
| `data[].primaryEmail` | string hoặc null | có | Email chính. |
| `data[].createdAt` | string (date-time) | có | Thời điểm tạo. ISO 8601, múi giờ UTC. |
| `data[].updatedAt` | string (date-time) | có | Thời điểm sửa gần nhất. ISO 8601, múi giờ UTC. |
| `data[].deletedAt` | string (date-time) hoặc null | có | Thời điểm xoá mềm; `null` nếu chưa xoá. ISO 8601 UTC hoặc `null`. |
| `nextCursor` | string hoặc null | có | Con trỏ của trang kế; `null` khi đã hết dữ liệu. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 400 | [`validation-failed`](/developers/errors#validation-failed) | Dữ liệu gửi lên không hợp lệ |
| 400 | [`invalid-cursor`](/developers/errors#invalid-cursor) | Con trỏ phân trang không hợp lệ |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).

## Tạo khách hàng {#createCustomer}

`POST /customers`

Tạo hồ sơ khách. Gửi `Idempotency-Key` để thử lại an toàn.

**Quyền cần có:** khoá có quyền `pos.customers.create`.

**Header**

| Tên | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | không | Khoá chống trùng do client đặt (khuyến nghị UUID). Gửi lại cùng khoá trong 24 giờ trả đúng kết quả lần đầu và header `Idempotent-Replayed: true`. |

**Thân yêu cầu (JSON)**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `name` | string | có | Tên khách. |
| `gender` | "male" \| "female" \| "other" hoặc null | không | Giới tính. |
| `dateOfBirth` | string hoặc null | không | Ngày sinh, dạng YYYY-MM-DD. |
| `contacts` | array<một trong nhiều dạng> | không | Số điện thoại và email. |
| `contacts[].kind` | "phone" | có |  |
| `contacts[].value` | string | có | Số điện thoại. |
| `contacts[].isPrimary` | boolean | không | Đặt làm số chính. |

**Ví dụ yêu cầu**

```bash
curl -X POST "https://danix.vn/api/open/v1/customers" \
  -H "Authorization: Bearer dnx_live_…" \
  -H "Idempotency-Key: 865d6735-58e7-488f-9ba7-be2de4de5651" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Nguyễn Văn An",
  "contacts": [
    {
      "kind": "phone",
      "value": "0901234567",
      "isPrimary": true
    }
  ]
}'
```

**Phản hồi mẫu (201)**

```json
{
  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
  "name": "Nguyễn Văn An",
  "gender": "male",
  "dateOfBirth": null,
  "source": "order",
  "isBlocked": false,
  "primaryPhone": "0901234567",
  "primaryEmail": null,
  "createdAt": "2026-10-02T03:15:00.000Z",
  "updatedAt": "2026-10-02T03:15:00.000Z",
  "deletedAt": null,
  "contacts": [
    {
      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000901",
      "kind": "phone",
      "value": "0901234567",
      "isPrimary": true
    }
  ],
  "addresses": []
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã khách hàng. |
| `name` | string | có | Tên khách. |
| `gender` | "male" \| "female" \| "other" hoặc null | có | Giới tính. |
| `dateOfBirth` | string hoặc null | có | Ngày sinh, dạng YYYY-MM-DD. |
| `source` | "manual" \| "order" \| "import" | có | Nguồn tạo hồ sơ: `manual`, `order` hoặc `import`. |
| `isBlocked` | boolean | có | Khách đang bị chặn. |
| `primaryPhone` | string hoặc null | có | Số điện thoại chính. |
| `primaryEmail` | string hoặc null | có | Email chính. |
| `createdAt` | string (date-time) | có | Thời điểm tạo. ISO 8601, múi giờ UTC. |
| `updatedAt` | string (date-time) | có | Thời điểm sửa gần nhất. ISO 8601, múi giờ UTC. |
| `deletedAt` | string (date-time) hoặc null | có | Thời điểm xoá mềm; `null` nếu chưa xoá. ISO 8601 UTC hoặc `null`. |
| `contacts` | array<object> | có | Số điện thoại và email. |
| `contacts[].id` | string (uuid) | có | Mã liên hệ. |
| `contacts[].kind` | "phone" \| "email" | có | Loại liên hệ. |
| `contacts[].value` | string | có | Giá trị đã chuẩn hoá (số điện thoại 0xxxxxxxxx hoặc email). |
| `contacts[].isPrimary` | boolean | có | Liên hệ chính của loại này. |
| `addresses` | array<object> | có | Địa chỉ giao hàng, địa chỉ dùng gần nhất đứng đầu. |
| `addresses[].id` | string (uuid) | có | Mã địa chỉ. |
| `addresses[].recipientName` | string hoặc null | có | Tên người nhận. |
| `addresses[].recipientPhone` | string hoặc null | có | Số điện thoại người nhận. |
| `addresses[].addressLine` | string | có | Số nhà, đường. |
| `addresses[].geoSystem` | "old" \| "new" hoặc null | có | Hệ địa giới của các mã bên dưới: `old` (tỉnh, huyện, xã) hoặc `new` (tỉnh, xã). `null` khi địa chỉ chỉ có chữ. |
| `addresses[].provinceUnitId` | string (uuid) hoặc null | có | Mã (UUID) tỉnh hoặc thành phố; `null` khi không có. |
| `addresses[].districtUnitId` | string (uuid) hoặc null | có | Mã (UUID) quận hoặc huyện; hệ `new` luôn `null`. |
| `addresses[].wardUnitId` | string (uuid) hoặc null | có | Mã (UUID) phường hoặc xã; `null` khi không có. |
| `addresses[].provinceName` | string hoặc null | có | Tỉnh hoặc thành phố. |
| `addresses[].districtName` | string hoặc null | có | Quận hoặc huyện. |
| `addresses[].wardName` | string hoặc null | có | Phường hoặc xã. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 402 | [`subscription-expired`](/developers/errors#subscription-expired) | Gói cước của shop đã hết hạn, chỉ còn quyền đọc |
| 400 | [`validation-failed`](/developers/errors#validation-failed) | Dữ liệu gửi lên không hợp lệ |
| 413 | [`payload-too-large`](/developers/errors#payload-too-large) | Thân yêu cầu vượt quá dung lượng cho phép |
| 415 | [`unsupported-media-type`](/developers/errors#unsupported-media-type) | Thân yêu cầu phải là JSON (`Content-Type: application/json`) |
| 409 | [`conflict`](/developers/errors#conflict) | Thao tác xung đột với trạng thái hiện tại |
| 409 | [`idempotency-key-in-progress`](/developers/errors#idempotency-key-in-progress) | Yêu cầu với cùng Idempotency-Key đang được xử lý |
| 422 | [`idempotency-key-reused`](/developers/errors#idempotency-key-reused) | Idempotency-Key đã dùng cho một yêu cầu có nội dung khác |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).

## Chi tiết một khách hàng {#getCustomer}

`GET /customers/{id}`

Trả khách kèm số điện thoại, email và địa chỉ giao hàng.

**Quyền cần có:** khoá có quyền `pos.customers.read`.

**Tham số đường dẫn**

| Tên | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã định danh (UUID) của tài nguyên. |

**Ví dụ yêu cầu**

```bash
curl -X GET "https://danix.vn/api/open/v1/customers/{id}" \
  -H "Authorization: Bearer dnx_live_…"
```

Thay `{id}` trong địa chỉ bằng mã thật của bản ghi, và `dnx_live_…` bằng khoá của bạn.

**Phản hồi mẫu (200)**

```json
{
  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
  "name": "Nguyễn Văn An",
  "gender": "male",
  "dateOfBirth": null,
  "source": "order",
  "isBlocked": false,
  "primaryPhone": "0901234567",
  "primaryEmail": null,
  "createdAt": "2026-10-02T03:15:00.000Z",
  "updatedAt": "2026-10-02T03:15:00.000Z",
  "deletedAt": null,
  "contacts": [
    {
      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000901",
      "kind": "phone",
      "value": "0901234567",
      "isPrimary": true
    }
  ],
  "addresses": []
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã khách hàng. |
| `name` | string | có | Tên khách. |
| `gender` | "male" \| "female" \| "other" hoặc null | có | Giới tính. |
| `dateOfBirth` | string hoặc null | có | Ngày sinh, dạng YYYY-MM-DD. |
| `source` | "manual" \| "order" \| "import" | có | Nguồn tạo hồ sơ: `manual`, `order` hoặc `import`. |
| `isBlocked` | boolean | có | Khách đang bị chặn. |
| `primaryPhone` | string hoặc null | có | Số điện thoại chính. |
| `primaryEmail` | string hoặc null | có | Email chính. |
| `createdAt` | string (date-time) | có | Thời điểm tạo. ISO 8601, múi giờ UTC. |
| `updatedAt` | string (date-time) | có | Thời điểm sửa gần nhất. ISO 8601, múi giờ UTC. |
| `deletedAt` | string (date-time) hoặc null | có | Thời điểm xoá mềm; `null` nếu chưa xoá. ISO 8601 UTC hoặc `null`. |
| `contacts` | array<object> | có | Số điện thoại và email. |
| `contacts[].id` | string (uuid) | có | Mã liên hệ. |
| `contacts[].kind` | "phone" \| "email" | có | Loại liên hệ. |
| `contacts[].value` | string | có | Giá trị đã chuẩn hoá (số điện thoại 0xxxxxxxxx hoặc email). |
| `contacts[].isPrimary` | boolean | có | Liên hệ chính của loại này. |
| `addresses` | array<object> | có | Địa chỉ giao hàng, địa chỉ dùng gần nhất đứng đầu. |
| `addresses[].id` | string (uuid) | có | Mã địa chỉ. |
| `addresses[].recipientName` | string hoặc null | có | Tên người nhận. |
| `addresses[].recipientPhone` | string hoặc null | có | Số điện thoại người nhận. |
| `addresses[].addressLine` | string | có | Số nhà, đường. |
| `addresses[].geoSystem` | "old" \| "new" hoặc null | có | Hệ địa giới của các mã bên dưới: `old` (tỉnh, huyện, xã) hoặc `new` (tỉnh, xã). `null` khi địa chỉ chỉ có chữ. |
| `addresses[].provinceUnitId` | string (uuid) hoặc null | có | Mã (UUID) tỉnh hoặc thành phố; `null` khi không có. |
| `addresses[].districtUnitId` | string (uuid) hoặc null | có | Mã (UUID) quận hoặc huyện; hệ `new` luôn `null`. |
| `addresses[].wardUnitId` | string (uuid) hoặc null | có | Mã (UUID) phường hoặc xã; `null` khi không có. |
| `addresses[].provinceName` | string hoặc null | có | Tỉnh hoặc thành phố. |
| `addresses[].districtName` | string hoặc null | có | Quận hoặc huyện. |
| `addresses[].wardName` | string hoặc null | có | Phường hoặc xã. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 404 | [`not-found`](/developers/errors#not-found) | Không tìm thấy tài nguyên |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).

## Sửa khách hàng {#updateCustomer}

`PATCH /customers/{id}`

Chỉ gửi trường cần đổi. Số điện thoại và email không sửa được qua đường này.

**Quyền cần có:** khoá có quyền `pos.customers.update`.

**Tham số đường dẫn**

| Tên | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã định danh (UUID) của tài nguyên. |

**Thân yêu cầu (JSON)**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `name` | string | không | Tên khách. |
| `gender` | "male" \| "female" \| "other" hoặc null | không | Giới tính. |
| `dateOfBirth` | string hoặc null | không | Ngày sinh, dạng YYYY-MM-DD. |

**Ví dụ yêu cầu**

```bash
curl -X PATCH "https://danix.vn/api/open/v1/customers/{id}" \
  -H "Authorization: Bearer dnx_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Nguyễn Văn An (VIP)"
}'
```

Thay `{id}` trong địa chỉ bằng mã thật của bản ghi, và `dnx_live_…` bằng khoá của bạn.

**Phản hồi mẫu (200)**

```json
{
  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
  "name": "Nguyễn Văn An",
  "gender": "male",
  "dateOfBirth": null,
  "source": "order",
  "isBlocked": false,
  "primaryPhone": "0901234567",
  "primaryEmail": null,
  "createdAt": "2026-10-02T03:15:00.000Z",
  "updatedAt": "2026-10-02T03:15:00.000Z",
  "deletedAt": null,
  "contacts": [
    {
      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000901",
      "kind": "phone",
      "value": "0901234567",
      "isPrimary": true
    }
  ],
  "addresses": []
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã khách hàng. |
| `name` | string | có | Tên khách. |
| `gender` | "male" \| "female" \| "other" hoặc null | có | Giới tính. |
| `dateOfBirth` | string hoặc null | có | Ngày sinh, dạng YYYY-MM-DD. |
| `source` | "manual" \| "order" \| "import" | có | Nguồn tạo hồ sơ: `manual`, `order` hoặc `import`. |
| `isBlocked` | boolean | có | Khách đang bị chặn. |
| `primaryPhone` | string hoặc null | có | Số điện thoại chính. |
| `primaryEmail` | string hoặc null | có | Email chính. |
| `createdAt` | string (date-time) | có | Thời điểm tạo. ISO 8601, múi giờ UTC. |
| `updatedAt` | string (date-time) | có | Thời điểm sửa gần nhất. ISO 8601, múi giờ UTC. |
| `deletedAt` | string (date-time) hoặc null | có | Thời điểm xoá mềm; `null` nếu chưa xoá. ISO 8601 UTC hoặc `null`. |
| `contacts` | array<object> | có | Số điện thoại và email. |
| `contacts[].id` | string (uuid) | có | Mã liên hệ. |
| `contacts[].kind` | "phone" \| "email" | có | Loại liên hệ. |
| `contacts[].value` | string | có | Giá trị đã chuẩn hoá (số điện thoại 0xxxxxxxxx hoặc email). |
| `contacts[].isPrimary` | boolean | có | Liên hệ chính của loại này. |
| `addresses` | array<object> | có | Địa chỉ giao hàng, địa chỉ dùng gần nhất đứng đầu. |
| `addresses[].id` | string (uuid) | có | Mã địa chỉ. |
| `addresses[].recipientName` | string hoặc null | có | Tên người nhận. |
| `addresses[].recipientPhone` | string hoặc null | có | Số điện thoại người nhận. |
| `addresses[].addressLine` | string | có | Số nhà, đường. |
| `addresses[].geoSystem` | "old" \| "new" hoặc null | có | Hệ địa giới của các mã bên dưới: `old` (tỉnh, huyện, xã) hoặc `new` (tỉnh, xã). `null` khi địa chỉ chỉ có chữ. |
| `addresses[].provinceUnitId` | string (uuid) hoặc null | có | Mã (UUID) tỉnh hoặc thành phố; `null` khi không có. |
| `addresses[].districtUnitId` | string (uuid) hoặc null | có | Mã (UUID) quận hoặc huyện; hệ `new` luôn `null`. |
| `addresses[].wardUnitId` | string (uuid) hoặc null | có | Mã (UUID) phường hoặc xã; `null` khi không có. |
| `addresses[].provinceName` | string hoặc null | có | Tỉnh hoặc thành phố. |
| `addresses[].districtName` | string hoặc null | có | Quận hoặc huyện. |
| `addresses[].wardName` | string hoặc null | có | Phường hoặc xã. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 402 | [`subscription-expired`](/developers/errors#subscription-expired) | Gói cước của shop đã hết hạn, chỉ còn quyền đọc |
| 400 | [`validation-failed`](/developers/errors#validation-failed) | Dữ liệu gửi lên không hợp lệ |
| 413 | [`payload-too-large`](/developers/errors#payload-too-large) | Thân yêu cầu vượt quá dung lượng cho phép |
| 415 | [`unsupported-media-type`](/developers/errors#unsupported-media-type) | Thân yêu cầu phải là JSON (`Content-Type: application/json`) |
| 404 | [`not-found`](/developers/errors#not-found) | Không tìm thấy tài nguyên |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).

## Thêm địa chỉ cho khách {#createCustomerAddress}

`POST /customers/{id}/addresses`

Thêm một địa chỉ giao hàng vào hồ sơ khách. Khách đã có một địa chỉ đúng như vậy thì trả `conflict`. Gửi mã đơn vị hành chính (tra bằng `GET /geo/units`) để đơn dùng địa chỉ này đẩy được sang hãng vận chuyển.

**Quyền cần có:** khoá có quyền `pos.customers.update`.

**Tham số đường dẫn**

| Tên | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã định danh (UUID) của tài nguyên. |

**Thân yêu cầu (JSON)**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `recipientName` | string hoặc null | không | Tên người nhận. |
| `recipientPhone` | string hoặc null | không | Số điện thoại người nhận. |
| `addressLine` | string | có | Số nhà, đường. |
| `provinceName` | string hoặc null | không | Tỉnh hoặc thành phố. |
| `districtName` | string hoặc null | không | Quận hoặc huyện. |
| `wardName` | string hoặc null | không | Phường hoặc xã. |
| `geoSystem` | "old" \| "new" hoặc null | không | Hệ địa giới của các mã bên dưới: `old` (tỉnh, huyện, xã) hoặc `new` (tỉnh, xã). Bỏ trống thì suy từ mã. |
| `provinceUnitId` | string (uuid) hoặc null | không | Mã (UUID) tỉnh hoặc thành phố trong danh mục hành chính của DANIX. |
| `districtUnitId` | string (uuid) hoặc null | không | Mã (UUID) quận hoặc huyện. Hệ `new` không có cấp này. |
| `wardUnitId` | string (uuid) hoặc null | không | Mã (UUID) phường hoặc xã. |

**Ví dụ yêu cầu**

```bash
curl -X POST "https://danix.vn/api/open/v1/customers/{id}/addresses" \
  -H "Authorization: Bearer dnx_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "addressLine": "12 Lê Lợi",
  "provinceName": "Đà Nẵng"
}'
```

Thay `{id}` trong địa chỉ bằng mã thật của bản ghi, và `dnx_live_…` bằng khoá của bạn.

**Phản hồi mẫu (201)**

```json
{
  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000999",
  "recipientName": null,
  "recipientPhone": null,
  "addressLine": "12 Lê Lợi, Đà Nẵng",
  "geoSystem": null,
  "provinceUnitId": null,
  "districtUnitId": null,
  "wardUnitId": null,
  "provinceName": null,
  "districtName": null,
  "wardName": null
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã địa chỉ. |
| `recipientName` | string hoặc null | có | Tên người nhận. |
| `recipientPhone` | string hoặc null | có | Số điện thoại người nhận. |
| `addressLine` | string | có | Số nhà, đường. |
| `geoSystem` | "old" \| "new" hoặc null | có | Hệ địa giới của các mã bên dưới: `old` (tỉnh, huyện, xã) hoặc `new` (tỉnh, xã). `null` khi địa chỉ chỉ có chữ. |
| `provinceUnitId` | string (uuid) hoặc null | có | Mã (UUID) tỉnh hoặc thành phố; `null` khi không có. |
| `districtUnitId` | string (uuid) hoặc null | có | Mã (UUID) quận hoặc huyện; hệ `new` luôn `null`. |
| `wardUnitId` | string (uuid) hoặc null | có | Mã (UUID) phường hoặc xã; `null` khi không có. |
| `provinceName` | string hoặc null | có | Tỉnh hoặc thành phố. |
| `districtName` | string hoặc null | có | Quận hoặc huyện. |
| `wardName` | string hoặc null | có | Phường hoặc xã. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 402 | [`subscription-expired`](/developers/errors#subscription-expired) | Gói cước của shop đã hết hạn, chỉ còn quyền đọc |
| 400 | [`validation-failed`](/developers/errors#validation-failed) | Dữ liệu gửi lên không hợp lệ |
| 413 | [`payload-too-large`](/developers/errors#payload-too-large) | Thân yêu cầu vượt quá dung lượng cho phép |
| 415 | [`unsupported-media-type`](/developers/errors#unsupported-media-type) | Thân yêu cầu phải là JSON (`Content-Type: application/json`) |
| 404 | [`not-found`](/developers/errors#not-found) | Không tìm thấy tài nguyên |
| 409 | [`conflict`](/developers/errors#conflict) | Thao tác xung đột với trạng thái hiện tại |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).

## Sửa địa chỉ của khách {#updateCustomerAddress}

`PATCH /customers/{id}/addresses/{addressId}`

Thay nội dung một địa chỉ giao hàng; trùng đúng một địa chỉ khác của khách thì trả `conflict`. Gửi tên địa danh (tỉnh, huyện, xã) mà không gửi mã thì mã đơn vị cũ bị XOÁ — địa chỉ không còn đẩy hãng được tới khi gửi lại mã; không gửi trường địa danh nào thì giữ nguyên.

**Quyền cần có:** khoá có quyền `pos.customers.update`.

**Tham số đường dẫn**

| Tên | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã định danh (UUID) của tài nguyên. |
| `addressId` | string (uuid) | có | Mã địa chỉ. |

**Thân yêu cầu (JSON)**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `recipientName` | string hoặc null | không | Tên người nhận. |
| `recipientPhone` | string hoặc null | không | Số điện thoại người nhận. |
| `addressLine` | string | có | Số nhà, đường. |
| `provinceName` | string hoặc null | không | Tỉnh hoặc thành phố. |
| `districtName` | string hoặc null | không | Quận hoặc huyện. |
| `wardName` | string hoặc null | không | Phường hoặc xã. |
| `geoSystem` | "old" \| "new" hoặc null | không | Hệ địa giới của các mã bên dưới: `old` (tỉnh, huyện, xã) hoặc `new` (tỉnh, xã). Bỏ trống thì suy từ mã. |
| `provinceUnitId` | string (uuid) hoặc null | không | Mã (UUID) tỉnh hoặc thành phố trong danh mục hành chính của DANIX. |
| `districtUnitId` | string (uuid) hoặc null | không | Mã (UUID) quận hoặc huyện. Hệ `new` không có cấp này. |
| `wardUnitId` | string (uuid) hoặc null | không | Mã (UUID) phường hoặc xã. |

**Ví dụ yêu cầu**

```bash
curl -X PATCH "https://danix.vn/api/open/v1/customers/{id}/addresses/{addressId}" \
  -H "Authorization: Bearer dnx_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "addressLine": "34 Trần Phú"
}'
```

Thay `{id}`, `{addressId}` trong địa chỉ bằng mã thật của bản ghi, và `dnx_live_…` bằng khoá của bạn.

**Phản hồi mẫu (200)**

```json
{
  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000999",
  "recipientName": null,
  "recipientPhone": null,
  "addressLine": "34 Trần Phú",
  "geoSystem": "old",
  "provinceUnitId": "018f3b8e-1c2d-7a4b-9c3d-000000001501",
  "districtUnitId": "018f3b8e-1c2d-7a4b-9c3d-000000001502",
  "wardUnitId": "018f3b8e-1c2d-7a4b-9c3d-000000001503",
  "provinceName": "Đà Nẵng",
  "districtName": "Hải Châu",
  "wardName": "Thạch Thang"
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã địa chỉ. |
| `recipientName` | string hoặc null | có | Tên người nhận. |
| `recipientPhone` | string hoặc null | có | Số điện thoại người nhận. |
| `addressLine` | string | có | Số nhà, đường. |
| `geoSystem` | "old" \| "new" hoặc null | có | Hệ địa giới của các mã bên dưới: `old` (tỉnh, huyện, xã) hoặc `new` (tỉnh, xã). `null` khi địa chỉ chỉ có chữ. |
| `provinceUnitId` | string (uuid) hoặc null | có | Mã (UUID) tỉnh hoặc thành phố; `null` khi không có. |
| `districtUnitId` | string (uuid) hoặc null | có | Mã (UUID) quận hoặc huyện; hệ `new` luôn `null`. |
| `wardUnitId` | string (uuid) hoặc null | có | Mã (UUID) phường hoặc xã; `null` khi không có. |
| `provinceName` | string hoặc null | có | Tỉnh hoặc thành phố. |
| `districtName` | string hoặc null | có | Quận hoặc huyện. |
| `wardName` | string hoặc null | có | Phường hoặc xã. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 402 | [`subscription-expired`](/developers/errors#subscription-expired) | Gói cước của shop đã hết hạn, chỉ còn quyền đọc |
| 400 | [`validation-failed`](/developers/errors#validation-failed) | Dữ liệu gửi lên không hợp lệ |
| 413 | [`payload-too-large`](/developers/errors#payload-too-large) | Thân yêu cầu vượt quá dung lượng cho phép |
| 415 | [`unsupported-media-type`](/developers/errors#unsupported-media-type) | Thân yêu cầu phải là JSON (`Content-Type: application/json`) |
| 404 | [`not-found`](/developers/errors#not-found) | Không tìm thấy tài nguyên |
| 409 | [`conflict`](/developers/errors#conflict) | Thao tác xung đột với trạng thái hiện tại |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).

## Xoá địa chỉ của khách {#deleteCustomerAddress}

`DELETE /customers/{id}/addresses/{addressId}`

Xoá một địa chỉ giao hàng khỏi hồ sơ khách. Đơn cũ vẫn giữ địa chỉ đã ghi.

**Quyền cần có:** khoá có quyền `pos.customers.delete`.

**Tham số đường dẫn**

| Tên | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã định danh (UUID) của tài nguyên. |
| `addressId` | string (uuid) | có | Mã địa chỉ. |

**Ví dụ yêu cầu**

```bash
curl -X DELETE "https://danix.vn/api/open/v1/customers/{id}/addresses/{addressId}" \
  -H "Authorization: Bearer dnx_live_…"
```

Thay `{id}`, `{addressId}` trong địa chỉ bằng mã thật của bản ghi, và `dnx_live_…` bằng khoá của bạn.

**Phản hồi mẫu (200)**

```json
{
  "ok": true
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `ok` | true | có | Luôn `true` khi thao tác thành công. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 402 | [`subscription-expired`](/developers/errors#subscription-expired) | Gói cước của shop đã hết hạn, chỉ còn quyền đọc |
| 404 | [`not-found`](/developers/errors#not-found) | Không tìm thấy tài nguyên |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).

## Ghi chú của khách {#listCustomerNotes}

`GET /customers/{id}/notes`

Danh sách ghi chú, mới nhất trước.

**Quyền cần có:** khoá có quyền `pos.customers.read`.

**Tham số đường dẫn**

| Tên | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã định danh (UUID) của tài nguyên. |

**Ví dụ yêu cầu**

```bash
curl -X GET "https://danix.vn/api/open/v1/customers/{id}/notes" \
  -H "Authorization: Bearer dnx_live_…"
```

Thay `{id}` trong địa chỉ bằng mã thật của bản ghi, và `dnx_live_…` bằng khoá của bạn.

**Phản hồi mẫu (200)**

```json
{
  "data": []
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `data` | array<object> | có | Các ghi chú, mới nhất trước. |
| `data[].id` | string (uuid) | có | Mã ghi chú. |
| `data[].message` | string | có | Nội dung ghi chú. |
| `data[].createdByName` | string hoặc null | có | Tên người hay ứng dụng đã ghi. |
| `data[].createdByIsIntegration` | boolean | có | `true` khi ghi chú do một ứng dụng kết nối tạo. |
| `data[].createdAt` | string (date-time) | có | Thời điểm tạo. ISO 8601, múi giờ UTC. |
| `data[].updatedAt` | string (date-time) | có | Thời điểm sửa gần nhất. ISO 8601, múi giờ UTC. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 404 | [`not-found`](/developers/errors#not-found) | Không tìm thấy tài nguyên |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).

## Thêm ghi chú vào khách {#createCustomerNote}

`POST /customers/{id}/notes`

Ghi chú đứng tên ứng dụng kết nối.

**Quyền cần có:** khoá có quyền `pos.customers.update`.

**Tham số đường dẫn**

| Tên | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã định danh (UUID) của tài nguyên. |

**Thân yêu cầu (JSON)**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `message` | string | có | Nội dung ghi chú. |

**Ví dụ yêu cầu**

```bash
curl -X POST "https://danix.vn/api/open/v1/customers/{id}/notes" \
  -H "Authorization: Bearer dnx_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "message": "Khách thích giao buổi tối"
}'
```

Thay `{id}` trong địa chỉ bằng mã thật của bản ghi, và `dnx_live_…` bằng khoá của bạn.

**Phản hồi mẫu (201)**

```json
{
  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000778",
  "message": "Khách thích giao buổi tối",
  "createdByName": "Đồng bộ kế toán",
  "createdByIsIntegration": true,
  "createdAt": "2026-10-02T03:15:00.000Z",
  "updatedAt": "2026-10-02T03:15:00.000Z"
}
```

**Trường của phản hồi**

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `id` | string (uuid) | có | Mã ghi chú. |
| `message` | string | có | Nội dung ghi chú. |
| `createdByName` | string hoặc null | có | Tên người hay ứng dụng đã ghi. |
| `createdByIsIntegration` | boolean | có | `true` khi ghi chú do một ứng dụng kết nối tạo. |
| `createdAt` | string (date-time) | có | Thời điểm tạo. ISO 8601, múi giờ UTC. |
| `updatedAt` | string (date-time) | có | Thời điểm sửa gần nhất. ISO 8601, múi giờ UTC. |

**Lỗi**

| Trạng thái | Mã `code` | Ý nghĩa |
| --- | --- | --- |
| 401 | [`invalid-api-key`](/developers/errors#invalid-api-key) | Khoá API thiếu, sai hoặc đã bị thu hồi |
| 403 | [`insufficient-permission`](/developers/errors#insufficient-permission) | Khoá API không có quyền thực hiện thao tác này |
| 403 | [`shop-suspended`](/developers/errors#shop-suspended) | Shop đang bị đình chỉ |
| 403 | [`automation-not-active`](/developers/errors#automation-not-active) | Shop chưa bật tính năng Tự động hoá |
| 429 | [`rate-limited`](/developers/errors#rate-limited) | Vượt hạn mức gọi API, hãy chờ rồi thử lại |
| 500 | [`internal-error`](/developers/errors#internal-error) | Lỗi hệ thống |
| 402 | [`subscription-expired`](/developers/errors#subscription-expired) | Gói cước của shop đã hết hạn, chỉ còn quyền đọc |
| 400 | [`validation-failed`](/developers/errors#validation-failed) | Dữ liệu gửi lên không hợp lệ |
| 413 | [`payload-too-large`](/developers/errors#payload-too-large) | Thân yêu cầu vượt quá dung lượng cho phép |
| 415 | [`unsupported-media-type`](/developers/errors#unsupported-media-type) | Thân yêu cầu phải là JSON (`Content-Type: application/json`) |
| 404 | [`not-found`](/developers/errors#not-found) | Không tìm thấy tài nguyên |

Định dạng lỗi và bảng mọi mã `code`: [Lỗi](/developers/errors).
