{
  "openapi": "3.0.3",
  "paths": {
    "/api/open/v1/shop": {
      "get": {
        "description": "Trả shop mà khoá API thuộc về, tên ứng dụng kết nối, các quyền được cấp, các trang chat được dùng và cờ chỉ đọc. Đây là cách nhanh nhất để kiểm khoá hoạt động.",
        "summary": "Thông tin shop của khoá",
        "tags": [],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Shop của khoá API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Shop"
                },
                "example": {
                  "id": "018f3b8e-0000-7000-8000-00000000a001",
                  "slug": "shop-mau",
                  "name": "Shop Mẫu",
                  "application": {
                    "name": "Đồng bộ kế toán",
                    "permissions": [
                      "pos.orders.read"
                    ],
                    "pageIds": []
                  },
                  "readOnly": false
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "getShop",
        "x-required-permissions": {
          "anyOf": [],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          }
        ]
      }
    },
    "/api/open/v1/geo/units": {
      "get": {
        "description": "Duyệt cây tỉnh → (huyện) → xã của một hệ địa giới, mỗi lượt một tầng: vắng `parentId` là các tỉnh, có `parentId` là các đơn vị con của nó. Không phân trang (mỗi tầng tối đa vài trăm dòng), sắp theo tên. Dùng `id` của kết quả làm `provinceUnitId`, `districtUnitId`, `wardUnitId` khi tạo đơn hay thêm địa chỉ khách — có mã thì đơn đẩy được sang hãng vận chuyển. `parentId` không có hoặc khác hệ trả `not-found`.",
        "summary": "Tra danh mục đơn vị hành chính",
        "tags": [],
        "parameters": [
          {
            "name": "system",
            "in": "query",
            "description": "Hệ địa giới cần tra: `old` hoặc `new`. Bắt buộc.",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "old",
                "new"
              ]
            }
          },
          {
            "name": "parentId",
            "in": "query",
            "description": "Mã đơn vị cha. Vắng thì trả các tỉnh, thành phố của hệ.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Lọc theo tên, không phân biệt hoa thường và dấu, trong tầng đang tra.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Một tầng của danh mục.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/GeoUnit"
                      },
                      "description": "Các đơn vị của tầng."
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "description": "Một tầng của danh mục."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000001501",
                      "system": "old",
                      "level": 1,
                      "parentId": null,
                      "name": "Đà Nẵng"
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listGeoUnits",
        "x-required-permissions": {
          "anyOf": [],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      }
    },
    "/api/open/v1/orders": {
      "get": {
        "description": "Phân trang theo con trỏ, sắp theo lần sửa tăng dần. Đồng bộ tăng dần bằng `updatedSince` (lật trang trong một lượt bằng `nextCursor`); dòng danh sách không có `lines`, `payments` và `shippingAddress` — lấy chi tiết bằng `GET /orders/{id}`.",
        "summary": "Liệt kê đơn hàng",
        "tags": [],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Số dòng mỗi trang, từ 1 đến 100. Mặc định 50.",
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "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.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "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).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?!0000-)"
            }
          },
          {
            "name": "includeDeleted",
            "in": "query",
            "description": "`true` để lấy cả bản ghi đã xoá (có `deletedAt`). Mặc định `false`.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Chỉ lấy đơn ở trạng thái này.",
            "schema": {
              "type": "string",
              "enum": [
                "new",
                "waiting_stock",
                "confirmed",
                "packing",
                "ready_to_ship",
                "shipped",
                "delivered",
                "paid",
                "returning",
                "partially_returned",
                "returned",
                "cancelled",
                "deleted"
              ]
            }
          },
          {
            "name": "customerId",
            "in": "query",
            "description": "Chỉ lấy đơn của khách này.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "code",
            "in": "query",
            "description": "Tìm đúng mã đơn.",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Một trang đơn hàng.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Order"
                      },
                      "description": "Các bản ghi của trang này."
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Con trỏ của trang kế; `null` khi đã hết dữ liệu.",
                      "nullable": true
                    }
                  },
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "description": "Một trang đơn hàng."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000101",
                      "code": "DH1024",
                      "status": "confirmed",
                      "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
                      "warehouseName": "Kho chính",
                      "customerId": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
                      "salesChannelId": "018f3b8e-1c2d-7a4b-9c3d-000000000301",
                      "salesChannelName": "Facebook",
                      "receivedAtShop": false,
                      "handlerUserId": "018f3b8e-1c2d-7a4b-9c3d-000000000401",
                      "handlerName": "Trần Thị Bình",
                      "billFullName": "Nguyễn Văn An",
                      "billPhone": "+84901234567",
                      "billEmail": null,
                      "totalPrice": "450000",
                      "discount": "0",
                      "shippingFee": "30000",
                      "freeShipping": false,
                      "surcharge": "0",
                      "tax": "0",
                      "totalAmount": "480000",
                      "returnedAmount": "0",
                      "exchangeReturn": false,
                      "paidAmount": "0",
                      "occurredAt": "2026-10-02T03:15:00.000Z",
                      "note": null,
                      "tags": [
                        {
                          "id": "018f3b8e-1c2d-7a4b-9c3d-000000000401",
                          "name": "Khách quen"
                        }
                      ],
                      "createdAt": "2026-10-02T03:15:00.000Z",
                      "updatedAt": "2026-10-02T03:15:00.000Z",
                      "deletedAt": null
                    }
                  ],
                  "nextCursor": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listOrders",
        "x-required-permissions": {
          "anyOf": [
            "pos.orders.read"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "invalid-cursor",
            "status": 400,
            "title": "Con trỏ phân trang không hợp lệ"
          }
        ]
      },
      "post": {
        "description": "Tạo đơn mới ở trạng thái `new`. Gửi kèm header `Idempotency-Key` để thử lại an toàn: cùng khoá trong 24 giờ trả đúng đơn đã tạo, không tạo đơn thứ hai. `unitPrice` khác giá niêm yết của mẫu mã, hay giảm giá (của dòng hoặc cả đơn) khác 0, cần thêm quyền `pos.orders.price.override`; gửi `payments` cần thêm `pos.orders.payment.record` — thiếu thì `insufficient-permission`. Đơn tạo qua API có đúng một người phụ trách ngay từ lúc tạo (riêng đơn do Tự động tạo đơn sinh có thể chưa có người phụ trách khi đang chờ chia — `handlerUserId` là `null`): gửi `handlerUserId` (người) hoặc `handlerRoleId` (vai trò, chia lần lượt) để chỉ định, không gửi cả hai; bỏ trống thì đơn chia lần lượt cho người của các vai trò \"chỉ thấy đơn của mình\", rồi ba người có nhiều đơn nhất 30 ngày gần nhất. Người hay vai được chỉ định mà không dùng được thì đơn vẫn tạo và chia theo bậc dưới (đọc `handlerUserId` của đơn trả về để biết ai nhận).",
        "summary": "Tạo đơn hàng",
        "tags": [],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "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`."
            }
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "warehouseId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Kho xuất hàng."
                  },
                  "customerId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Khách hàng có sẵn. Bỏ trống nếu bán lẻ."
                  },
                  "salesChannelId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Kênh bán."
                  },
                  "receivedAtShop": {
                    "type": "boolean",
                    "description": "Khách nhận hàng tại shop."
                  },
                  "billFullName": {
                    "type": "string",
                    "maxLength": 200,
                    "nullable": true,
                    "description": "Tên người mua."
                  },
                  "billPhone": {
                    "type": "string",
                    "maxLength": 32,
                    "nullable": true,
                    "description": "Số điện thoại người mua."
                  },
                  "billEmail": {
                    "type": "string",
                    "maxLength": 320,
                    "nullable": true,
                    "description": "Email người mua."
                  },
                  "shippingAddress": {
                    "type": "object",
                    "properties": {
                      "recipientName": {
                        "type": "string",
                        "maxLength": 200,
                        "nullable": true,
                        "description": "Tên người nhận."
                      },
                      "recipientPhone": {
                        "type": "string",
                        "maxLength": 32,
                        "nullable": true,
                        "description": "Số điện thoại người nhận."
                      },
                      "addressLine": {
                        "type": "string",
                        "maxLength": 500,
                        "nullable": true,
                        "description": "Số nhà, đường."
                      },
                      "provinceName": {
                        "type": "string",
                        "maxLength": 120,
                        "nullable": true,
                        "description": "Tỉnh hoặc thành phố."
                      },
                      "districtName": {
                        "type": "string",
                        "maxLength": 120,
                        "nullable": true,
                        "description": "Quận hoặc huyện."
                      },
                      "wardName": {
                        "type": "string",
                        "maxLength": 120,
                        "nullable": true,
                        "description": "Phường hoặc xã."
                      },
                      "geoSystem": {
                        "type": "string",
                        "enum": [
                          "old",
                          "new"
                        ],
                        "nullable": true,
                        "description": "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": {
                        "type": "string",
                        "format": "uuid",
                        "nullable": true,
                        "description": "Mã (UUID) tỉnh hoặc thành phố trong danh mục hành chính của DANIX."
                      },
                      "districtUnitId": {
                        "type": "string",
                        "format": "uuid",
                        "nullable": true,
                        "description": "Mã (UUID) quận hoặc huyện. Hệ `new` không có cấp này."
                      },
                      "wardUnitId": {
                        "type": "string",
                        "format": "uuid",
                        "nullable": true,
                        "description": "Mã (UUID) phường hoặc xã."
                      }
                    },
                    "nullable": true,
                    "description": "Địa chỉ giao hàng."
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "variantId": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Mã mẫu mã cần bán."
                        },
                        "quantity": {
                          "type": "string",
                          "pattern": "^\\d{1,9}(\\.\\d{1,3})?$",
                          "description": "Số lượng, lớn hơn 0. Chuỗi thập phân, tối đa ba chữ số lẻ."
                        },
                        "unitPrice": {
                          "type": "string",
                          "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                          "description": "Đơn giá. Khác giá niêm yết của mẫu mã thì khoá cần thêm quyền `pos.orders.price.override`. Chuỗi thập phân, ví dụ \"150000\"."
                        },
                        "discount": {
                          "type": "string",
                          "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                          "description": "Giảm giá của dòng. Mặc định \"0\". Khác 0 thì khoá cần thêm quyền `pos.orders.price.override`."
                        }
                      },
                      "required": [
                        "variantId",
                        "quantity",
                        "unitPrice"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 200,
                    "description": "Các dòng hàng, 1 đến 200."
                  },
                  "discount": {
                    "type": "string",
                    "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                    "description": "Giảm giá cả đơn. Khác 0 thì khoá cần thêm quyền `pos.orders.price.override`."
                  },
                  "shippingFee": {
                    "type": "string",
                    "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                    "description": "Phí vận chuyển khách trả."
                  },
                  "freeShipping": {
                    "type": "boolean",
                    "description": "Shop chịu phí giao hàng."
                  },
                  "surcharge": {
                    "type": "string",
                    "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                    "description": "Phụ thu."
                  },
                  "tax": {
                    "type": "string",
                    "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                    "description": "Thuế."
                  },
                  "payments": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "methodId": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Mã phương thức thanh toán."
                        },
                        "amount": {
                          "type": "string",
                          "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                          "description": "Số tiền thu. Chuỗi thập phân, ví dụ \"150000\"."
                        }
                      },
                      "required": [
                        "methodId",
                        "amount"
                      ]
                    },
                    "description": "Các khoản thu. Gửi khoản thu thì khoá cần thêm quyền `pos.orders.payment.record`."
                  },
                  "occurredAt": {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?!0000-)",
                    "description": "Thời điểm phát sinh đơn."
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 2000,
                    "nullable": true,
                    "description": "Ghi chú nội bộ."
                  },
                  "tagIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 50,
                    "description": "Các thẻ gắn vào đơn."
                  },
                  "handlerUserId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Người phụ trách đơn: id từ `GET /staff`. Không gửi cả `handlerUserId` lẫn `handlerRoleId`. Người không dùng được (đã nghỉ, bị vô hiệu hoá, thiếu quyền sửa đơn) thì hệ thống KHÔNG từ chối mà chia theo bậc dưới; bỏ trống thì đơn chia theo vai trò \"chỉ thấy đơn của mình\", rồi ba người nhiều đơn nhất 30 ngày gần nhất."
                  },
                  "handlerRoleId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Vai trò nhận đơn: id từ `GET /roles`. Đơn chia lần lượt cho người của vai trò ấy; vai trò không còn ai dùng được thì chia theo bậc dưới. Không gửi cả `handlerUserId` lẫn `handlerRoleId`."
                  }
                },
                "required": [
                  "warehouseId",
                  "lines"
                ],
                "description": "Thân tạo đơn."
              },
              "example": {
                "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
                "billFullName": "Nguyễn Văn An",
                "billPhone": "+84901234567",
                "lines": [
                  {
                    "variantId": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
                    "quantity": "2",
                    "unitPrice": "225000"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Một đơn hàng.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000101",
                  "code": "DH1024",
                  "status": "confirmed",
                  "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
                  "warehouseName": "Kho chính",
                  "customerId": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
                  "salesChannelId": "018f3b8e-1c2d-7a4b-9c3d-000000000301",
                  "salesChannelName": "Facebook",
                  "receivedAtShop": false,
                  "handlerUserId": "018f3b8e-1c2d-7a4b-9c3d-000000000401",
                  "handlerName": "Trần Thị Bình",
                  "billFullName": "Nguyễn Văn An",
                  "billPhone": "+84901234567",
                  "billEmail": null,
                  "shippingAddress": {
                    "recipientName": "Nguyễn Văn An",
                    "recipientPhone": "+84901234567",
                    "addressLine": "12 Lê Lợi",
                    "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"
                  },
                  "totalPrice": "450000",
                  "discount": "0",
                  "shippingFee": "30000",
                  "freeShipping": false,
                  "surcharge": "0",
                  "tax": "0",
                  "totalAmount": "480000",
                  "returnedAmount": "0",
                  "exchangeReturn": false,
                  "paidAmount": "0",
                  "occurredAt": "2026-10-02T03:15:00.000Z",
                  "note": null,
                  "tags": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000401",
                      "name": "Khách quen"
                    }
                  ],
                  "lines": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000501",
                      "variantId": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
                      "productId": "018f3b8e-1c2d-7a4b-9c3d-000000000701",
                      "variantCode": "AO-THUN-M",
                      "variantName": "Áo thun - M",
                      "productName": "Áo thun cổ tròn",
                      "quantity": "2",
                      "unitPrice": "225000",
                      "discount": "0",
                      "lineTotal": "450000",
                      "returnedQuantity": "0"
                    }
                  ],
                  "payments": [],
                  "createdAt": "2026-10-02T03:15:00.000Z",
                  "updatedAt": "2026-10-02T03:15:00.000Z",
                  "deletedAt": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              },
              "Idempotent-Replayed": {
                "description": "`true` khi đây là kết quả của lần gửi trước với cùng `Idempotency-Key`.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Dữ liệu hợp lệ nhưng nghiệp vụ không cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "createOrder",
        "x-required-permissions": {
          "anyOf": [
            "pos.orders.create"
          ],
          "allOf": [
            "pos.products.read"
          ]
        },
        "x-optional-permissions": [
          "pos.orders.payment.record",
          "pos.orders.price.override"
        ],
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "conflict",
            "status": 409,
            "title": "Thao tác xung đột với trạng thái hiện tại"
          },
          {
            "code": "idempotency-key-in-progress",
            "status": 409,
            "title": "Yêu cầu với cùng Idempotency-Key đang được xử lý"
          },
          {
            "code": "idempotency-key-reused",
            "status": 422,
            "title": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác"
          },
          {
            "code": "customer-blocked",
            "status": 422,
            "title": "Khách hàng đang bị chặn"
          },
          {
            "code": "insufficient-stock",
            "status": 422,
            "title": "Không đủ tồn kho cho thao tác này"
          }
        ],
        "x-idempotent": true
      }
    },
    "/api/open/v1/orders/{id}": {
      "get": {
        "description": "Trả đơn kèm dòng hàng, khoản thu và địa chỉ giao hàng.",
        "summary": "Chi tiết một đơn hàng",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "responses": {
          "200": {
            "description": "Một đơn hàng.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000101",
                  "code": "DH1024",
                  "status": "confirmed",
                  "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
                  "warehouseName": "Kho chính",
                  "customerId": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
                  "salesChannelId": "018f3b8e-1c2d-7a4b-9c3d-000000000301",
                  "salesChannelName": "Facebook",
                  "receivedAtShop": false,
                  "handlerUserId": "018f3b8e-1c2d-7a4b-9c3d-000000000401",
                  "handlerName": "Trần Thị Bình",
                  "billFullName": "Nguyễn Văn An",
                  "billPhone": "+84901234567",
                  "billEmail": null,
                  "shippingAddress": {
                    "recipientName": "Nguyễn Văn An",
                    "recipientPhone": "+84901234567",
                    "addressLine": "12 Lê Lợi",
                    "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"
                  },
                  "totalPrice": "450000",
                  "discount": "0",
                  "shippingFee": "30000",
                  "freeShipping": false,
                  "surcharge": "0",
                  "tax": "0",
                  "totalAmount": "480000",
                  "returnedAmount": "0",
                  "exchangeReturn": false,
                  "paidAmount": "0",
                  "occurredAt": "2026-10-02T03:15:00.000Z",
                  "note": null,
                  "tags": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000401",
                      "name": "Khách quen"
                    }
                  ],
                  "lines": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000501",
                      "variantId": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
                      "productId": "018f3b8e-1c2d-7a4b-9c3d-000000000701",
                      "variantCode": "AO-THUN-M",
                      "variantName": "Áo thun - M",
                      "productName": "Áo thun cổ tròn",
                      "quantity": "2",
                      "unitPrice": "225000",
                      "discount": "0",
                      "lineTotal": "450000",
                      "returnedQuantity": "0"
                    }
                  ],
                  "payments": [],
                  "createdAt": "2026-10-02T03:15:00.000Z",
                  "updatedAt": "2026-10-02T03:15:00.000Z",
                  "deletedAt": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "getOrder",
        "x-required-permissions": {
          "anyOf": [
            "pos.orders.read"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          }
        ]
      },
      "patch": {
        "description": "Chỉ gửi trường cần đổi. Dòng hàng và kho không sửa được qua đường này. Đổi `discount` cần thêm quyền `pos.orders.price.override`. Gửi `handlerUserId` là đổi người phụ trách đơn, cần thêm quyền `pos.orders.assign` — thiếu thì `insufficient-permission`; người nhận không dùng được thì `validation-failed`. Lượt đổi người phụ trách được áp TRƯỚC và ĐỘC LẬP với phần còn lại của thân: nếu các trường khác bị từ chối, phản hồi là lỗi nhưng người phụ trách ĐÃ đổi — đọc lại đơn (`GET /orders/{id}`) để biết trạng thái thật trước khi thử lại.",
        "summary": "Sửa đơn hàng",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customerId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Đổi khách hàng gắn với đơn."
                  },
                  "salesChannelId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Đổi kênh bán."
                  },
                  "billFullName": {
                    "type": "string",
                    "maxLength": 200,
                    "nullable": true,
                    "description": "Tên người mua."
                  },
                  "billPhone": {
                    "type": "string",
                    "maxLength": 32,
                    "nullable": true,
                    "description": "Số điện thoại người mua."
                  },
                  "billEmail": {
                    "type": "string",
                    "maxLength": 320,
                    "nullable": true,
                    "description": "Email người mua."
                  },
                  "shippingAddress": {
                    "type": "object",
                    "properties": {
                      "recipientName": {
                        "type": "string",
                        "maxLength": 200,
                        "nullable": true,
                        "description": "Tên người nhận."
                      },
                      "recipientPhone": {
                        "type": "string",
                        "maxLength": 32,
                        "nullable": true,
                        "description": "Số điện thoại người nhận."
                      },
                      "addressLine": {
                        "type": "string",
                        "maxLength": 500,
                        "nullable": true,
                        "description": "Số nhà, đường."
                      },
                      "provinceName": {
                        "type": "string",
                        "maxLength": 120,
                        "nullable": true,
                        "description": "Tỉnh hoặc thành phố."
                      },
                      "districtName": {
                        "type": "string",
                        "maxLength": 120,
                        "nullable": true,
                        "description": "Quận hoặc huyện."
                      },
                      "wardName": {
                        "type": "string",
                        "maxLength": 120,
                        "nullable": true,
                        "description": "Phường hoặc xã."
                      },
                      "geoSystem": {
                        "type": "string",
                        "enum": [
                          "old",
                          "new"
                        ],
                        "nullable": true,
                        "description": "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": {
                        "type": "string",
                        "format": "uuid",
                        "nullable": true,
                        "description": "Mã (UUID) tỉnh hoặc thành phố trong danh mục hành chính của DANIX."
                      },
                      "districtUnitId": {
                        "type": "string",
                        "format": "uuid",
                        "nullable": true,
                        "description": "Mã (UUID) quận hoặc huyện. Hệ `new` không có cấp này."
                      },
                      "wardUnitId": {
                        "type": "string",
                        "format": "uuid",
                        "nullable": true,
                        "description": "Mã (UUID) phường hoặc xã."
                      }
                    },
                    "nullable": true,
                    "description": "Địa chỉ giao hàng."
                  },
                  "discount": {
                    "type": "string",
                    "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                    "description": "Giảm giá cả đơn. Đổi giảm giá thì khoá cần thêm quyền `pos.orders.price.override`."
                  },
                  "shippingFee": {
                    "type": "string",
                    "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                    "description": "Phí vận chuyển khách trả."
                  },
                  "surcharge": {
                    "type": "string",
                    "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                    "description": "Phụ thu."
                  },
                  "tax": {
                    "type": "string",
                    "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                    "description": "Thuế."
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 2000,
                    "nullable": true,
                    "description": "Ghi chú nội bộ."
                  },
                  "tagIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 50,
                    "description": "Thay toàn bộ thẻ của đơn."
                  },
                  "handlerUserId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Đổi người phụ trách đơn: id từ `GET /staff`. Đòi thêm quyền `pos.orders.assign` (thiếu thì `insufficient-permission`); người nhận phải là thành viên đang làm việc có quyền sửa đơn (không thì `validation-failed`). Đổi người phụ trách phát sự kiện `order.updated`."
                  }
                },
                "description": "Thân sửa đơn. Chỉ gửi trường cần đổi."
              },
              "example": {
                "note": "Khách dặn gọi trước khi giao"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Một đơn hàng.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000101",
                  "code": "DH1024",
                  "status": "confirmed",
                  "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
                  "warehouseName": "Kho chính",
                  "customerId": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
                  "salesChannelId": "018f3b8e-1c2d-7a4b-9c3d-000000000301",
                  "salesChannelName": "Facebook",
                  "receivedAtShop": false,
                  "handlerUserId": "018f3b8e-1c2d-7a4b-9c3d-000000000401",
                  "handlerName": "Trần Thị Bình",
                  "billFullName": "Nguyễn Văn An",
                  "billPhone": "+84901234567",
                  "billEmail": null,
                  "shippingAddress": {
                    "recipientName": "Nguyễn Văn An",
                    "recipientPhone": "+84901234567",
                    "addressLine": "12 Lê Lợi",
                    "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"
                  },
                  "totalPrice": "450000",
                  "discount": "0",
                  "shippingFee": "30000",
                  "freeShipping": false,
                  "surcharge": "0",
                  "tax": "0",
                  "totalAmount": "480000",
                  "returnedAmount": "0",
                  "exchangeReturn": false,
                  "paidAmount": "0",
                  "occurredAt": "2026-10-02T03:15:00.000Z",
                  "note": null,
                  "tags": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000401",
                      "name": "Khách quen"
                    }
                  ],
                  "lines": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000501",
                      "variantId": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
                      "productId": "018f3b8e-1c2d-7a4b-9c3d-000000000701",
                      "variantCode": "AO-THUN-M",
                      "variantName": "Áo thun - M",
                      "productName": "Áo thun cổ tròn",
                      "quantity": "2",
                      "unitPrice": "225000",
                      "discount": "0",
                      "lineTotal": "450000",
                      "returnedQuantity": "0"
                    }
                  ],
                  "payments": [],
                  "createdAt": "2026-10-02T03:15:00.000Z",
                  "updatedAt": "2026-10-02T03:15:00.000Z",
                  "deletedAt": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Dữ liệu hợp lệ nhưng nghiệp vụ không cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "updateOrder",
        "x-required-permissions": {
          "anyOf": [
            "pos.orders.update"
          ],
          "allOf": [
            "pos.products.read"
          ]
        },
        "x-optional-permissions": [
          "pos.orders.price.override",
          "pos.orders.assign"
        ],
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "conflict",
            "status": 409,
            "title": "Thao tác xung đột với trạng thái hiện tại"
          },
          {
            "code": "customer-blocked",
            "status": 422,
            "title": "Khách hàng đang bị chặn"
          },
          {
            "code": "order-has-live-shipment",
            "status": 409,
            "title": "Đơn đang có vận đơn chưa kết thúc nên không thao tác được"
          }
        ]
      }
    },
    "/api/open/v1/orders/{id}/status": {
      "post": {
        "description": "Chuyển đơn sang trạng thái mới theo luồng của shop. Chuyển trạng thái có thể xuất hoặc nhập kho; từ chối bằng `order-status-not-allowed` hoặc `insufficient-stock`. Huỷ một đơn đã gửi hàng (`shipped`, `delivered`, `paid`, `returning`, `partially_returned`, `returned` sang `cancelled`; hàng khách đang giữ được nhập lại kho) cần thêm quyền `pos.orders.delete` — thiếu thì `insufficient-permission`. Đơn đã rời `new` thì không đưa về `new` được nữa (`order-status-not-allowed`). Đích phải nằm trong ma trận chuyển trạng thái (cùng ma trận của người dùng trên web): ví dụ `returned` chỉ còn sang `cancelled`, `new` không nhảy thẳng sang `delivered` / `paid` / `returning`; ngoài ma trận → `order-status-not-allowed`.",
        "summary": "Đổi trạng thái đơn",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "new",
                      "waiting_stock",
                      "confirmed",
                      "packing",
                      "ready_to_ship",
                      "shipped",
                      "delivered",
                      "paid",
                      "returning",
                      "returned",
                      "cancelled"
                    ],
                    "description": "Trạng thái mới của đơn. `new` không bao giờ là một đích đi được: đơn đang ở `new` → `conflict`; đơn đã rời `new` → `order-status-not-allowed`."
                  }
                },
                "required": [
                  "status"
                ],
                "description": "Thân đổi trạng thái đơn."
              },
              "example": {
                "status": "confirmed"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Một đơn hàng.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000101",
                  "code": "DH1024",
                  "status": "confirmed",
                  "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
                  "warehouseName": "Kho chính",
                  "customerId": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
                  "salesChannelId": "018f3b8e-1c2d-7a4b-9c3d-000000000301",
                  "salesChannelName": "Facebook",
                  "receivedAtShop": false,
                  "handlerUserId": "018f3b8e-1c2d-7a4b-9c3d-000000000401",
                  "handlerName": "Trần Thị Bình",
                  "billFullName": "Nguyễn Văn An",
                  "billPhone": "+84901234567",
                  "billEmail": null,
                  "shippingAddress": {
                    "recipientName": "Nguyễn Văn An",
                    "recipientPhone": "+84901234567",
                    "addressLine": "12 Lê Lợi",
                    "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"
                  },
                  "totalPrice": "450000",
                  "discount": "0",
                  "shippingFee": "30000",
                  "freeShipping": false,
                  "surcharge": "0",
                  "tax": "0",
                  "totalAmount": "480000",
                  "returnedAmount": "0",
                  "exchangeReturn": false,
                  "paidAmount": "0",
                  "occurredAt": "2026-10-02T03:15:00.000Z",
                  "note": null,
                  "tags": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000401",
                      "name": "Khách quen"
                    }
                  ],
                  "lines": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000501",
                      "variantId": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
                      "productId": "018f3b8e-1c2d-7a4b-9c3d-000000000701",
                      "variantCode": "AO-THUN-M",
                      "variantName": "Áo thun - M",
                      "productName": "Áo thun cổ tròn",
                      "quantity": "2",
                      "unitPrice": "225000",
                      "discount": "0",
                      "lineTotal": "450000",
                      "returnedQuantity": "0"
                    }
                  ],
                  "payments": [],
                  "createdAt": "2026-10-02T03:15:00.000Z",
                  "updatedAt": "2026-10-02T03:15:00.000Z",
                  "deletedAt": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Dữ liệu hợp lệ nhưng nghiệp vụ không cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "changeOrderStatus",
        "x-required-permissions": {
          "anyOf": [
            "pos.orders.status"
          ],
          "allOf": [
            "pos.products.read"
          ]
        },
        "x-optional-permissions": [
          "pos.orders.delete"
        ],
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "conflict",
            "status": 409,
            "title": "Thao tác xung đột với trạng thái hiện tại"
          },
          {
            "code": "order-status-not-allowed",
            "status": 422,
            "title": "Không chuyển được đơn sang trạng thái này từ trạng thái hiện tại"
          },
          {
            "code": "insufficient-stock",
            "status": 422,
            "title": "Không đủ tồn kho cho thao tác này"
          },
          {
            "code": "order-has-live-shipment",
            "status": 409,
            "title": "Đơn đang có vận đơn chưa kết thúc nên không thao tác được"
          }
        ]
      }
    },
    "/api/open/v1/orders/{id}/notes": {
      "get": {
        "description": "Danh sách ghi chú, mới nhất trước.",
        "summary": "Ghi chú của đơn",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "responses": {
          "200": {
            "description": "Danh sách ghi chú.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Note"
                      },
                      "description": "Các ghi chú, mới nhất trước."
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "description": "Danh sách ghi chú."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000777",
                      "message": "Đã gọi khách xác nhận",
                      "createdByName": "Đồng bộ kế toán",
                      "createdByIsIntegration": true,
                      "createdAt": "2026-10-02T03:15:00.000Z",
                      "updatedAt": "2026-10-02T03:15:00.000Z"
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listOrderNotes",
        "x-required-permissions": {
          "anyOf": [
            "pos.orders.read"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      },
      "post": {
        "description": "Ghi chú đứng tên ứng dụng kết nối.",
        "summary": "Thêm ghi chú vào đơn",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000,
                    "description": "Nội dung ghi chú."
                  }
                },
                "required": [
                  "message"
                ],
                "description": "Thân thêm ghi chú."
              },
              "example": {
                "message": "Đã gọi khách xác nhận"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Một ghi chú.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000777",
                  "message": "Đã gọi khách xác nhận",
                  "createdByName": "Đồng bộ kế toán",
                  "createdByIsIntegration": true,
                  "createdAt": "2026-10-02T03:15:00.000Z",
                  "updatedAt": "2026-10-02T03:15:00.000Z"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "createOrderNote",
        "x-required-permissions": {
          "anyOf": [
            "pos.orders.update"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      }
    },
    "/api/open/v1/staff": {
      "get": {
        "description": "Các nhân viên đang làm việc trong shop (không gồm tài khoản hệ thống, ứng dụng kết nối hay người bị vô hiệu hoá), chỉ mã và tên. Dùng `id` làm `handlerUserId` khi tạo đơn hay đổi người phụ trách. Người nào chọn được làm người phụ trách còn tuỳ quyền sửa đơn của họ — chọn người không dùng được thì tạo đơn chia theo bậc dưới, đổi người phụ trách thì `validation-failed`.",
        "summary": "Danh sách nhân viên",
        "tags": [],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Danh sách nhân viên.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Staff"
                      },
                      "description": "Các nhân viên."
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "description": "Danh sách nhân viên."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000a01",
                      "name": "Nguyễn Văn An"
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listStaff",
        "x-required-permissions": {
          "anyOf": [
            "pos.orders.create"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          }
        ]
      }
    },
    "/api/open/v1/roles": {
      "get": {
        "description": "Các vai trò của shop (không gồm vai trò của ứng dụng kết nối), chỉ mã và tên. Dùng `id` làm `handlerRoleId` khi tạo đơn: đơn chia lần lượt cho những người của vai trò ấy có quyền sửa đơn.",
        "summary": "Danh sách vai trò",
        "tags": [],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Danh sách vai trò.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Role"
                      },
                      "description": "Các vai trò."
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "description": "Danh sách vai trò."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000b01",
                      "name": "Bán hàng"
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listRoles",
        "x-required-permissions": {
          "anyOf": [
            "pos.orders.create"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          }
        ]
      }
    },
    "/api/open/v1/products": {
      "get": {
        "description": "Phân trang theo con trỏ, sắp theo lần sửa tăng dần. Dòng danh sách gọn; lấy mẫu mã bằng `GET /products/{id}`.",
        "summary": "Liệt kê sản phẩm",
        "tags": [],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Số dòng mỗi trang, từ 1 đến 100. Mặc định 50.",
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "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.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "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).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?!0000-)"
            }
          },
          {
            "name": "includeDeleted",
            "in": "query",
            "description": "`true` để lấy cả bản ghi đã xoá (có `deletedAt`). Mặc định `false`.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Tìm theo tên hoặc mã sản phẩm.",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "categoryId",
            "in": "query",
            "description": "Chỉ lấy sản phẩm trong danh mục này.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Một trang sản phẩm.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProductSummary"
                      },
                      "description": "Các bản ghi của trang này."
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Con trỏ của trang kế; `null` khi đã hết dữ liệu.",
                      "nullable": true
                    }
                  },
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "description": "Một trang sản phẩm."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000701",
                      "code": "SP0001",
                      "name": "Áo thun cổ tròn",
                      "type": "simple",
                      "isActive": true,
                      "categoryNames": [
                        "Áo"
                      ],
                      "variantCount": 1,
                      "imageUrl": null,
                      "priceFrom": "225000",
                      "priceTo": "225000",
                      "updatedAt": "2026-10-02T03:15:00.000Z",
                      "deletedAt": null
                    }
                  ],
                  "nextCursor": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listProducts",
        "x-required-permissions": {
          "anyOf": [
            "pos.products.read"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "invalid-cursor",
            "status": 400,
            "title": "Con trỏ phân trang không hợp lệ"
          }
        ]
      },
      "post": {
        "description": "Tạo sản phẩm kèm ít nhất một mẫu mã. Sản phẩm nhiều mẫu mã: mỗi mẫu mang `attributes` theo tên (`{ \"Size\": \"M\" }`), không hai mẫu trùng tổ hợp; thuộc tính hay giá trị chưa có trong sản phẩm thì được thêm vào sản phẩm.",
        "summary": "Tạo sản phẩm",
        "tags": [],
        "parameters": [],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "description": "Mã sản phẩm. Bỏ trống thì hệ thống tự cấp."
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Tên sản phẩm."
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 5000,
                    "nullable": true,
                    "description": "Mô tả."
                  },
                  "categoryIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "Các danh mục."
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "simple",
                      "service"
                    ],
                    "description": "Loại sản phẩm. Mặc định `simple`."
                  },
                  "variants": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Mã mẫu mã cần sửa; bỏ trống để thêm mẫu mã mới."
                        },
                        "code": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "Mã mẫu mã (SKU). Bỏ trống thì hệ thống tự cấp."
                        },
                        "barcode": {
                          "type": "string",
                          "maxLength": 64,
                          "nullable": true,
                          "description": "Mã vạch."
                        },
                        "price": {
                          "type": "string",
                          "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                          "description": "Giá bán."
                        },
                        "weightGrams": {
                          "type": "string",
                          "pattern": "^\\d{1,9}(\\.\\d{1,3})?$",
                          "nullable": true,
                          "description": "Cân nặng, gram."
                        },
                        "isActive": {
                          "type": "boolean",
                          "description": "Mẫu mã còn bán."
                        },
                        "attributes": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 80
                          },
                          "description": "Thuộc tính theo TÊN, ví dụ `{ \"Size\": \"M\", \"Màu\": \"Đỏ\" }` (tên và giá trị không phân biệt hoa thường; chưa có trong sản phẩm thì được thêm vào sản phẩm). Không hai mẫu của một sản phẩm trùng tổ hợp. Gửi kèm `id` là đổi tổ hợp của mẫu mã ấy; bỏ trống khi gửi `id` là giữ tổ hợp đang lưu. Sản phẩm tối đa 10 thuộc tính, mỗi thuộc tính tối đa 100 giá trị."
                        },
                        "keywords": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 50
                          },
                          "maxItems": 20,
                          "description": "Từ khoá chốt đơn của mẫu mã (tối đa 20, mỗi từ khoá 1–50 ký tự, một dòng). Bỏ trống là giữ nguyên; gửi mảng là THAY trọn tập, mảng rỗng là gỡ hết. Duy nhất trong shop, không phân biệt hoa thường — trùng thì 409 `variant-keyword-taken`; trùng mã sản phẩm / mã mẫu mã của sản phẩm hay mẫu khác thì 409 `match-key-taken` (từ khoá bằng mã của CHÍNH mẫu ấy vẫn được)."
                        }
                      }
                    },
                    "minItems": 1,
                    "description": "Các mẫu mã, ít nhất một. Nhiều mẫu mã thì mỗi mẫu mang `attributes` để phân biệt; thứ tự thuộc tính của sản phẩm theo thứ tự khoá ở mẫu mã đầu tiên."
                  }
                },
                "required": [
                  "name",
                  "variants"
                ],
                "description": "Thân tạo sản phẩm."
              },
              "example": {
                "name": "Áo thun cổ tròn",
                "variants": [
                  {
                    "code": "AO-THUN-M",
                    "price": "225000",
                    "attributes": {
                      "Size": "M"
                    },
                    "keywords": [
                      "AOM"
                    ]
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Một sản phẩm kèm mẫu mã và ảnh.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000701",
                  "code": "SP0001",
                  "name": "Áo thun cổ tròn",
                  "description": null,
                  "type": "simple",
                  "isActive": true,
                  "categoryIds": [
                    "018f3b8e-1c2d-7a4b-9c3d-000000000801"
                  ],
                  "variants": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
                      "code": "AO-THUN-M",
                      "barcode": null,
                      "price": "225000",
                      "weightGrams": "200",
                      "lengthCm": null,
                      "widthCm": null,
                      "heightCm": null,
                      "isActive": true,
                      "attributes": {
                        "Size": "M"
                      },
                      "keywords": [
                        "AOM"
                      ]
                    }
                  ],
                  "images": [],
                  "createdAt": "2026-10-02T03:15:00.000Z",
                  "deletedAt": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "createProduct",
        "x-required-permissions": {
          "anyOf": [
            "pos.products.create"
          ],
          "allOf": [
            "pos.products.read"
          ]
        },
        "x-optional-permissions": [
          "pos.products.update"
        ],
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "duplicate-code",
            "status": 409,
            "title": "Mã đã tồn tại trong shop"
          },
          {
            "code": "variant-combo-duplicate",
            "status": 409,
            "title": "Hai mẫu mã của sản phẩm trùng tổ hợp thuộc tính"
          },
          {
            "code": "variant-keyword-taken",
            "status": 409,
            "title": "Từ khoá chốt đơn đã thuộc mẫu mã khác"
          },
          {
            "code": "match-key-taken",
            "status": 409,
            "title": "Mã sản phẩm, mã mẫu mã hoặc từ khoá chốt đơn trùng khoá khớp của sản phẩm / mẫu mã khác"
          },
          {
            "code": "conflict",
            "status": 409,
            "title": "Thao tác xung đột với trạng thái hiện tại"
          }
        ]
      }
    },
    "/api/open/v1/products/{id}": {
      "get": {
        "description": "Trả sản phẩm kèm mẫu mã, ảnh và danh mục.",
        "summary": "Chi tiết một sản phẩm",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "responses": {
          "200": {
            "description": "Một sản phẩm kèm mẫu mã và ảnh.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000701",
                  "code": "SP0001",
                  "name": "Áo thun cổ tròn",
                  "description": null,
                  "type": "simple",
                  "isActive": true,
                  "categoryIds": [
                    "018f3b8e-1c2d-7a4b-9c3d-000000000801"
                  ],
                  "variants": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
                      "code": "AO-THUN-M",
                      "barcode": null,
                      "price": "225000",
                      "weightGrams": "200",
                      "lengthCm": null,
                      "widthCm": null,
                      "heightCm": null,
                      "isActive": true,
                      "attributes": {
                        "Size": "M"
                      },
                      "keywords": [
                        "AOM"
                      ]
                    }
                  ],
                  "images": [],
                  "createdAt": "2026-10-02T03:15:00.000Z",
                  "deletedAt": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "getProduct",
        "x-required-permissions": {
          "anyOf": [
            "pos.products.read"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      },
      "patch": {
        "description": "Chỉ gửi trường cần đổi. `variants` vá TỪNG PHẦN: mẫu mã có `id` chỉ đổi các trường gửi lên, mẫu mã không có `id` được thêm vào cuối (kèm `attributes` theo tên), mẫu mã đã có mà không nhắc tới giữ nguyên. `keywords` của một mẫu mã: vắng là giữ, mảng là thay trọn tập (mảng rỗng gỡ hết). Gỡ mẫu mã chưa mở ở phiên bản này.",
        "summary": "Sửa sản phẩm",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Tên sản phẩm."
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 5000,
                    "nullable": true,
                    "description": "Mô tả."
                  },
                  "categoryIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "Thay toàn bộ danh mục."
                  },
                  "variants": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Mã mẫu mã cần sửa; bỏ trống để thêm mẫu mã mới."
                        },
                        "code": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "Mã mẫu mã (SKU). Bỏ trống thì hệ thống tự cấp."
                        },
                        "barcode": {
                          "type": "string",
                          "maxLength": 64,
                          "nullable": true,
                          "description": "Mã vạch."
                        },
                        "price": {
                          "type": "string",
                          "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                          "description": "Giá bán."
                        },
                        "weightGrams": {
                          "type": "string",
                          "pattern": "^\\d{1,9}(\\.\\d{1,3})?$",
                          "nullable": true,
                          "description": "Cân nặng, gram."
                        },
                        "isActive": {
                          "type": "boolean",
                          "description": "Mẫu mã còn bán."
                        },
                        "attributes": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 80
                          },
                          "description": "Thuộc tính theo TÊN, ví dụ `{ \"Size\": \"M\", \"Màu\": \"Đỏ\" }` (tên và giá trị không phân biệt hoa thường; chưa có trong sản phẩm thì được thêm vào sản phẩm). Không hai mẫu của một sản phẩm trùng tổ hợp. Gửi kèm `id` là đổi tổ hợp của mẫu mã ấy; bỏ trống khi gửi `id` là giữ tổ hợp đang lưu. Sản phẩm tối đa 10 thuộc tính, mỗi thuộc tính tối đa 100 giá trị."
                        },
                        "keywords": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 50
                          },
                          "maxItems": 20,
                          "description": "Từ khoá chốt đơn của mẫu mã (tối đa 20, mỗi từ khoá 1–50 ký tự, một dòng). Bỏ trống là giữ nguyên; gửi mảng là THAY trọn tập, mảng rỗng là gỡ hết. Duy nhất trong shop, không phân biệt hoa thường — trùng thì 409 `variant-keyword-taken`; trùng mã sản phẩm / mã mẫu mã của sản phẩm hay mẫu khác thì 409 `match-key-taken` (từ khoá bằng mã của CHÍNH mẫu ấy vẫn được)."
                        }
                      }
                    },
                    "minItems": 1,
                    "description": "Vá TỪNG PHẦN: mẫu mã có `id` được sửa (chỉ các trường gửi lên), mẫu mã không có `id` được thêm vào cuối; mẫu mã đã có mà không nhắc tới thì GIỮ NGUYÊN. Gỡ mẫu mã chưa mở ở phiên bản này."
                  }
                },
                "description": "Thân sửa sản phẩm. Chỉ gửi trường cần đổi."
              },
              "example": {
                "name": "Áo thun cổ tròn cotton",
                "variants": [
                  {
                    "code": "AO-THUN-L",
                    "price": "235000",
                    "attributes": {
                      "Size": "L"
                    }
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Một sản phẩm kèm mẫu mã và ảnh.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000701",
                  "code": "SP0001",
                  "name": "Áo thun cổ tròn",
                  "description": null,
                  "type": "simple",
                  "isActive": true,
                  "categoryIds": [
                    "018f3b8e-1c2d-7a4b-9c3d-000000000801"
                  ],
                  "variants": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
                      "code": "AO-THUN-M",
                      "barcode": null,
                      "price": "225000",
                      "weightGrams": "200",
                      "lengthCm": null,
                      "widthCm": null,
                      "heightCm": null,
                      "isActive": true,
                      "attributes": {
                        "Size": "M"
                      },
                      "keywords": [
                        "AOM"
                      ]
                    }
                  ],
                  "images": [],
                  "createdAt": "2026-10-02T03:15:00.000Z",
                  "deletedAt": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "updateProduct",
        "x-required-permissions": {
          "anyOf": [
            "pos.products.update"
          ],
          "allOf": [
            "pos.products.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "duplicate-code",
            "status": 409,
            "title": "Mã đã tồn tại trong shop"
          },
          {
            "code": "variant-combo-duplicate",
            "status": 409,
            "title": "Hai mẫu mã của sản phẩm trùng tổ hợp thuộc tính"
          },
          {
            "code": "variant-keyword-taken",
            "status": 409,
            "title": "Từ khoá chốt đơn đã thuộc mẫu mã khác"
          },
          {
            "code": "match-key-taken",
            "status": 409,
            "title": "Mã sản phẩm, mã mẫu mã hoặc từ khoá chốt đơn trùng khoá khớp của sản phẩm / mẫu mã khác"
          },
          {
            "code": "conflict",
            "status": 409,
            "title": "Thao tác xung đột với trạng thái hiện tại"
          }
        ]
      }
    },
    "/api/open/v1/categories": {
      "get": {
        "description": "Toàn bộ danh mục của shop, dạng cây phẳng với `parentId`.",
        "summary": "Danh mục sản phẩm",
        "tags": [],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Danh sách danh mục.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Category"
                      },
                      "description": "Các danh mục."
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "description": "Danh sách danh mục."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000801",
                      "parentId": null,
                      "name": "Áo",
                      "slug": "ao"
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listCategories",
        "x-required-permissions": {
          "anyOf": [
            "pos.products.read"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          }
        ]
      }
    },
    "/api/open/v1/warehouses": {
      "get": {
        "description": "Các kho hàng của shop.",
        "summary": "Danh sách kho",
        "tags": [],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Danh sách kho.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Warehouse"
                      },
                      "description": "Các kho."
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "description": "Danh sách kho."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
                      "code": "KHO1",
                      "name": "Kho chính",
                      "phone": null,
                      "addressLine": null,
                      "provinceName": null,
                      "districtName": null,
                      "wardName": null,
                      "isDefault": true
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listWarehouses",
        "x-required-permissions": {
          "anyOf": [
            "pos.inventory.read"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          }
        ]
      }
    },
    "/api/open/v1/inventory/stock": {
      "get": {
        "description": "Mỗi dòng là tồn của một mẫu mã tại một kho. Không có giá vốn. Đồng bộ tăng dần bằng `updatedSince` (lật trang trong một lượt bằng `nextCursor`): có `updatedSince` thì chỉ trả dòng tồn đổi từ mốc ấy và đã qua khoảng trễ an toàn (bằng 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). Mốc của dòng tồn chỉ đổi khi số tồn hoặc `inTransit` đổi; giá, mã, mã vạch, tên và ảnh đi kèm lấy từ đồng bộ sản phẩm và danh sách kho.",
        "summary": "Tồn kho theo mẫu mã và kho",
        "tags": [],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Số dòng mỗi trang, từ 1 đến 100. Mặc định 50.",
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "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.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "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).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?!0000-)"
            }
          },
          {
            "name": "warehouseId",
            "in": "query",
            "description": "Chỉ lấy tồn của kho này.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Tìm theo tên hoặc mã mẫu mã.",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Một trang tồn kho.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StockRow"
                      },
                      "description": "Các bản ghi của trang này."
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Con trỏ của trang kế; `null` khi đã hết dữ liệu.",
                      "nullable": true
                    }
                  },
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "description": "Một trang tồn kho."
                },
                "example": {
                  "data": [
                    {
                      "variantId": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
                      "productId": "018f3b8e-1c2d-7a4b-9c3d-000000000701",
                      "productName": "Áo thun cổ tròn",
                      "variantName": "Áo thun - M",
                      "code": "AO-THUN-M",
                      "barcode": null,
                      "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
                      "warehouseName": "Kho chính",
                      "quantity": "18",
                      "inTransit": "0",
                      "price": "225000"
                    }
                  ],
                  "nextCursor": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listStock",
        "x-required-permissions": {
          "anyOf": [
            "pos.inventory.read"
          ],
          "allOf": [
            "pos.products.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "invalid-cursor",
            "status": 400,
            "title": "Con trỏ phân trang không hợp lệ"
          }
        ]
      }
    },
    "/api/open/v1/inventory/documents": {
      "post": {
        "description": "Tạo phiếu nhập, xuất hoặc kiểm kho ở trạng thái `draft`. Tồn kho chỉ đổi khi ghi sổ bằng `POST /inventory/documents/{id}/post`.",
        "summary": "Tạo phiếu kho nháp",
        "tags": [],
        "parameters": [],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "receipt",
                      "issue",
                      "stocktake"
                    ],
                    "description": "Loại phiếu: nhập, xuất hoặc kiểm kho."
                  },
                  "warehouseId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Kho của phiếu."
                  },
                  "supplierId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Nhà cung cấp (phiếu nhập)."
                  },
                  "documentDate": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Ngày của phiếu, dạng YYYY-MM-DD."
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 1000,
                    "nullable": true,
                    "description": "Ghi chú."
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "variantId": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Mã mẫu mã."
                        },
                        "quantity": {
                          "type": "string",
                          "pattern": "^\\d{1,9}(\\.\\d{1,3})?$",
                          "description": "Số lượng nhập hoặc xuất."
                        },
                        "countedQuantity": {
                          "type": "string",
                          "pattern": "^\\d{1,9}(\\.\\d{1,3})?$",
                          "description": "Số đếm thực tế (phiếu kiểm kho)."
                        },
                        "unitCost": {
                          "type": "string",
                          "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                          "description": "Giá nhập (phiếu nhập)."
                        },
                        "note": {
                          "type": "string",
                          "maxLength": 500,
                          "nullable": true,
                          "description": "Ghi chú dòng."
                        }
                      },
                      "required": [
                        "variantId"
                      ]
                    },
                    "maxItems": 500,
                    "description": "Các dòng của phiếu, tối đa 500."
                  }
                },
                "required": [
                  "kind",
                  "warehouseId"
                ],
                "description": "Thân tạo phiếu kho nháp. Ghi sổ bằng `POST /inventory/documents/{id}/post`."
              },
              "example": {
                "kind": "receipt",
                "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
                "lines": [
                  {
                    "variantId": "018f3b8e-1c2d-7a4b-9c3d-000000000601",
                    "quantity": "10",
                    "unitCost": "120000"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Một phiếu kho.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InventoryDocument"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000001201",
                  "kind": "receipt",
                  "code": "PN0001",
                  "status": "draft",
                  "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
                  "supplierId": null,
                  "occurredAt": "2026-10-02T03:15:00.000Z",
                  "postedAt": null,
                  "costingMethod": null,
                  "note": null,
                  "lines": []
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Dữ liệu hợp lệ nhưng nghiệp vụ không cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "createInventoryDocument",
        "x-required-permissions": {
          "anyOf": [
            "pos.inventory.receipt",
            "pos.inventory.adjust"
          ],
          "allOf": [
            "pos.inventory.read",
            "pos.products.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "variant-removed",
            "status": 422,
            "title": "Mẫu mã đã bị gỡ khỏi sản phẩm"
          }
        ]
      }
    },
    "/api/open/v1/inventory/documents/{id}/post": {
      "post": {
        "description": "Ghi sổ phiếu nháp: tồn kho đổi ngay. Ghi sổ lần hai trả `document-already-posted`. Quyền theo LOẠI phiếu: phiếu kiểm kho cần `pos.inventory.adjust`, phiếu nhập hay xuất cần `pos.inventory.receipt` — có quyền còn lại thôi thì `insufficient-permission`.",
        "summary": "Ghi sổ phiếu kho",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "responses": {
          "200": {
            "description": "Một phiếu kho.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InventoryDocument"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000001201",
                  "kind": "receipt",
                  "code": "PN0001",
                  "status": "posted",
                  "warehouseId": "018f3b8e-1c2d-7a4b-9c3d-000000000001",
                  "supplierId": null,
                  "occurredAt": "2026-10-02T03:15:00.000Z",
                  "postedAt": "2026-10-02T03:16:00.000Z",
                  "costingMethod": "average",
                  "note": null,
                  "lines": []
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Dữ liệu hợp lệ nhưng nghiệp vụ không cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "postInventoryDocument",
        "x-required-permissions": {
          "anyOf": [
            "pos.inventory.receipt",
            "pos.inventory.adjust"
          ],
          "allOf": [
            "pos.inventory.read",
            "pos.products.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "conflict",
            "status": 409,
            "title": "Thao tác xung đột với trạng thái hiện tại"
          },
          {
            "code": "document-already-posted",
            "status": 409,
            "title": "Phiếu kho đã ghi sổ"
          },
          {
            "code": "insufficient-stock",
            "status": 422,
            "title": "Không đủ tồn kho cho thao tác này"
          },
          {
            "code": "variant-removed",
            "status": 422,
            "title": "Mẫu mã đã bị gỡ khỏi sản phẩm"
          }
        ]
      }
    },
    "/api/open/v1/customers": {
      "get": {
        "description": "Phân trang theo con trỏ. Lọc theo số điện thoại bằng `phone` (nhận `0…`, `84…`, `+84…`). Mọi số điện thoại trả về ở dạng `+84…`.",
        "summary": "Liệt kê khách hàng",
        "tags": [],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Số dòng mỗi trang, từ 1 đến 100. Mặc định 50.",
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "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.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "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).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?!0000-)"
            }
          },
          {
            "name": "includeDeleted",
            "in": "query",
            "description": "`true` để lấy cả bản ghi đã xoá (có `deletedAt`). Mặc định `false`.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "phone",
            "in": "query",
            "description": "Tìm đúng số điện thoại (chấp nhận 0xxxxxxxxx hoặc +84…).",
            "schema": {
              "type": "string",
              "maxLength": 20
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Tìm theo tên.",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Một trang khách hàng.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomerSummary"
                      },
                      "description": "Các bản ghi của trang này."
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Con trỏ của trang kế; `null` khi đã hết dữ liệu.",
                      "nullable": true
                    }
                  },
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "description": "Một trang khách hàng."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
                      "name": "Nguyễn Văn An",
                      "gender": "male",
                      "dateOfBirth": null,
                      "source": "order",
                      "isBlocked": false,
                      "primaryPhone": "+84901234567",
                      "primaryEmail": null,
                      "createdAt": "2026-10-02T03:15:00.000Z",
                      "updatedAt": "2026-10-02T03:15:00.000Z",
                      "deletedAt": null
                    }
                  ],
                  "nextCursor": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listCustomers",
        "x-required-permissions": {
          "anyOf": [
            "pos.customers.read"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "invalid-cursor",
            "status": 400,
            "title": "Con trỏ phân trang không hợp lệ"
          }
        ]
      },
      "post": {
        "description": "Tạo hồ sơ khách. Gửi `Idempotency-Key` để thử lại an toàn.",
        "summary": "Tạo khách hàng",
        "tags": [],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "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`."
            }
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Tên khách."
                  },
                  "gender": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female",
                      "other"
                    ],
                    "nullable": true,
                    "description": "Giới tính."
                  },
                  "dateOfBirth": {
                    "type": "string",
                    "nullable": true,
                    "description": "Ngày sinh, dạng YYYY-MM-DD."
                  },
                  "contacts": {
                    "type": "array",
                    "items": {
                      "discriminator": {
                        "propertyName": "kind"
                      },
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "kind": {
                              "type": "string",
                              "enum": [
                                "phone"
                              ]
                            },
                            "value": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 20,
                              "description": "Số điện thoại."
                            },
                            "isPrimary": {
                              "type": "boolean",
                              "description": "Đặt làm số chính."
                            }
                          },
                          "required": [
                            "kind",
                            "value"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "kind": {
                              "type": "string",
                              "enum": [
                                "email"
                              ]
                            },
                            "value": {
                              "type": "string",
                              "format": "email",
                              "description": "Email."
                            },
                            "isPrimary": {
                              "type": "boolean",
                              "description": "Đặt làm email chính."
                            }
                          },
                          "required": [
                            "kind",
                            "value"
                          ]
                        }
                      ]
                    },
                    "maxItems": 20,
                    "description": "Số điện thoại và email."
                  }
                },
                "required": [
                  "name"
                ],
                "description": "Thân tạo khách hàng."
              },
              "example": {
                "name": "Nguyễn Văn An",
                "contacts": [
                  {
                    "kind": "phone",
                    "value": "+84901234567",
                    "isPrimary": true
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Một khách hàng kèm liên hệ và địa chỉ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
                  "name": "Nguyễn Văn An",
                  "gender": "male",
                  "dateOfBirth": null,
                  "source": "order",
                  "isBlocked": false,
                  "primaryPhone": "+84901234567",
                  "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": "+84901234567",
                      "isPrimary": true
                    }
                  ],
                  "addresses": []
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              },
              "Idempotent-Replayed": {
                "description": "`true` khi đây là kết quả của lần gửi trước với cùng `Idempotency-Key`.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Dữ liệu hợp lệ nhưng nghiệp vụ không cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "createCustomer",
        "x-required-permissions": {
          "anyOf": [
            "pos.customers.create"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "conflict",
            "status": 409,
            "title": "Thao tác xung đột với trạng thái hiện tại"
          },
          {
            "code": "idempotency-key-in-progress",
            "status": 409,
            "title": "Yêu cầu với cùng Idempotency-Key đang được xử lý"
          },
          {
            "code": "idempotency-key-reused",
            "status": 422,
            "title": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác"
          }
        ],
        "x-idempotent": true
      }
    },
    "/api/open/v1/customers/{id}": {
      "get": {
        "description": "Trả khách kèm số điện thoại, email và địa chỉ giao hàng.",
        "summary": "Chi tiết một khách hàng",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "responses": {
          "200": {
            "description": "Một khách hàng kèm liên hệ và địa chỉ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
                  "name": "Nguyễn Văn An",
                  "gender": "male",
                  "dateOfBirth": null,
                  "source": "order",
                  "isBlocked": false,
                  "primaryPhone": "+84901234567",
                  "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": "+84901234567",
                      "isPrimary": true
                    }
                  ],
                  "addresses": []
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "getCustomer",
        "x-required-permissions": {
          "anyOf": [
            "pos.customers.read"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      },
      "patch": {
        "description": "Chỉ gửi trường cần đổi. Số điện thoại và email không sửa được qua đường này.",
        "summary": "Sửa khách hàng",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Tên khách."
                  },
                  "gender": {
                    "type": "string",
                    "enum": [
                      "male",
                      "female",
                      "other"
                    ],
                    "nullable": true,
                    "description": "Giới tính."
                  },
                  "dateOfBirth": {
                    "type": "string",
                    "nullable": true,
                    "description": "Ngày sinh, dạng YYYY-MM-DD."
                  }
                },
                "description": "Thân sửa khách hàng. Chỉ gửi trường cần đổi."
              },
              "example": {
                "name": "Nguyễn Văn An (VIP)"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Một khách hàng kèm liên hệ và địa chỉ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000000201",
                  "name": "Nguyễn Văn An",
                  "gender": "male",
                  "dateOfBirth": null,
                  "source": "order",
                  "isBlocked": false,
                  "primaryPhone": "+84901234567",
                  "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": "+84901234567",
                      "isPrimary": true
                    }
                  ],
                  "addresses": []
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "updateCustomer",
        "x-required-permissions": {
          "anyOf": [
            "pos.customers.update"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      }
    },
    "/api/open/v1/customers/{id}/addresses": {
      "post": {
        "description": "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.",
        "summary": "Thêm địa chỉ cho khách",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "recipientName": {
                    "type": "string",
                    "maxLength": 200,
                    "nullable": true,
                    "description": "Tên người nhận."
                  },
                  "recipientPhone": {
                    "type": "string",
                    "maxLength": 32,
                    "nullable": true,
                    "description": "Số điện thoại người nhận."
                  },
                  "addressLine": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "description": "Số nhà, đường."
                  },
                  "provinceName": {
                    "type": "string",
                    "maxLength": 120,
                    "nullable": true,
                    "description": "Tỉnh hoặc thành phố."
                  },
                  "districtName": {
                    "type": "string",
                    "maxLength": 120,
                    "nullable": true,
                    "description": "Quận hoặc huyện."
                  },
                  "wardName": {
                    "type": "string",
                    "maxLength": 120,
                    "nullable": true,
                    "description": "Phường hoặc xã."
                  },
                  "geoSystem": {
                    "type": "string",
                    "enum": [
                      "old",
                      "new"
                    ],
                    "nullable": true,
                    "description": "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": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Mã (UUID) tỉnh hoặc thành phố trong danh mục hành chính của DANIX."
                  },
                  "districtUnitId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Mã (UUID) quận hoặc huyện. Hệ `new` không có cấp này."
                  },
                  "wardUnitId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Mã (UUID) phường hoặc xã."
                  }
                },
                "required": [
                  "addressLine"
                ],
                "description": "Thân thêm hoặc sửa địa chỉ giao hàng."
              },
              "example": {
                "addressLine": "12 Lê Lợi",
                "provinceName": "Đà Nẵng"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Một địa chỉ giao hàng của khách.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerAddress"
                },
                "example": {
                  "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
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "createCustomerAddress",
        "x-required-permissions": {
          "anyOf": [
            "pos.customers.update"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "conflict",
            "status": 409,
            "title": "Thao tác xung đột với trạng thái hiện tại"
          }
        ]
      }
    },
    "/api/open/v1/customers/{id}/addresses/{addressId}": {
      "patch": {
        "description": "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.",
        "summary": "Sửa địa chỉ của khách",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          },
          {
            "name": "addressId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã địa chỉ."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "recipientName": {
                    "type": "string",
                    "maxLength": 200,
                    "nullable": true,
                    "description": "Tên người nhận."
                  },
                  "recipientPhone": {
                    "type": "string",
                    "maxLength": 32,
                    "nullable": true,
                    "description": "Số điện thoại người nhận."
                  },
                  "addressLine": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "description": "Số nhà, đường."
                  },
                  "provinceName": {
                    "type": "string",
                    "maxLength": 120,
                    "nullable": true,
                    "description": "Tỉnh hoặc thành phố."
                  },
                  "districtName": {
                    "type": "string",
                    "maxLength": 120,
                    "nullable": true,
                    "description": "Quận hoặc huyện."
                  },
                  "wardName": {
                    "type": "string",
                    "maxLength": 120,
                    "nullable": true,
                    "description": "Phường hoặc xã."
                  },
                  "geoSystem": {
                    "type": "string",
                    "enum": [
                      "old",
                      "new"
                    ],
                    "nullable": true,
                    "description": "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": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Mã (UUID) tỉnh hoặc thành phố trong danh mục hành chính của DANIX."
                  },
                  "districtUnitId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Mã (UUID) quận hoặc huyện. Hệ `new` không có cấp này."
                  },
                  "wardUnitId": {
                    "type": "string",
                    "format": "uuid",
                    "nullable": true,
                    "description": "Mã (UUID) phường hoặc xã."
                  }
                },
                "required": [
                  "addressLine"
                ],
                "description": "Thân thêm hoặc sửa địa chỉ giao hàng."
              },
              "example": {
                "addressLine": "34 Trần Phú"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Một địa chỉ giao hàng của khách.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerAddress"
                },
                "example": {
                  "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"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "updateCustomerAddress",
        "x-required-permissions": {
          "anyOf": [
            "pos.customers.update"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "conflict",
            "status": 409,
            "title": "Thao tác xung đột với trạng thái hiện tại"
          }
        ]
      },
      "delete": {
        "description": "Xoá một địa chỉ giao hàng khỏi hồ sơ khách. Đơn cũ vẫn giữ địa chỉ đã ghi.",
        "summary": "Xoá địa chỉ của khách",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          },
          {
            "name": "addressId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã địa chỉ."
          }
        ],
        "responses": {
          "200": {
            "description": "Thao tác thành công, không có dữ liệu trả về.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Luôn `true` khi thao tác thành công."
                    }
                  },
                  "required": [
                    "ok"
                  ],
                  "description": "Thao tác thành công, không có dữ liệu trả về."
                },
                "example": {
                  "ok": true
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "deleteCustomerAddress",
        "x-required-permissions": {
          "anyOf": [
            "pos.customers.delete"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      }
    },
    "/api/open/v1/customers/{id}/notes": {
      "get": {
        "description": "Danh sách ghi chú, mới nhất trước.",
        "summary": "Ghi chú của khách",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "responses": {
          "200": {
            "description": "Danh sách ghi chú.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Note"
                      },
                      "description": "Các ghi chú, mới nhất trước."
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "description": "Danh sách ghi chú."
                },
                "example": {
                  "data": []
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listCustomerNotes",
        "x-required-permissions": {
          "anyOf": [
            "pos.customers.read"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      },
      "post": {
        "description": "Ghi chú đứng tên ứng dụng kết nối.",
        "summary": "Thêm ghi chú vào khách",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000,
                    "description": "Nội dung ghi chú."
                  }
                },
                "required": [
                  "message"
                ],
                "description": "Thân thêm ghi chú."
              },
              "example": {
                "message": "Khách thích giao buổi tối"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Một ghi chú.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                },
                "example": {
                  "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"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "createCustomerNote",
        "x-required-permissions": {
          "anyOf": [
            "pos.customers.update"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      }
    },
    "/api/open/v1/shipments": {
      "get": {
        "description": "Phân trang theo con trỏ. Dòng danh sách không có hành trình; lấy bằng `GET /shipments/{id}`.",
        "summary": "Liệt kê vận đơn",
        "tags": [],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Số dòng mỗi trang, từ 1 đến 100. Mặc định 50.",
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "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.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "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).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?!0000-)"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Chỉ lấy vận đơn ở trạng thái này.",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "delivering",
                "delivered",
                "delivery_failed",
                "returning",
                "returned",
                "partially_returned",
                "cancelled",
                "lost",
                "damaged",
                "exception",
                "scrapped"
              ]
            }
          },
          {
            "name": "orderId",
            "in": "query",
            "description": "Chỉ lấy vận đơn của đơn này.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Một trang vận đơn.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ShipmentSummary"
                      },
                      "description": "Các bản ghi của trang này."
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Con trỏ của trang kế; `null` khi đã hết dữ liệu.",
                      "nullable": true
                    }
                  },
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "description": "Một trang vận đơn."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000001001",
                      "carrier": "ghn",
                      "trackingCode": "GHNABC123",
                      "status": "delivering",
                      "orderId": "018f3b8e-1c2d-7a4b-9c3d-000000000101",
                      "orderCode": "DH1024",
                      "codAmount": "480000",
                      "feeTotal": "30000",
                      "createdAt": "2026-10-02T03:15:00.000Z",
                      "expectedDeliveryAt": null,
                      "failReason": null,
                      "updatedAt": "2026-10-02T03:15:00.000Z"
                    }
                  ],
                  "nextCursor": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listShipments",
        "x-required-permissions": {
          "anyOf": [
            "pos.shipping.read"
          ],
          "allOf": [
            "pos.orders.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "invalid-cursor",
            "status": 400,
            "title": "Con trỏ phân trang không hợp lệ"
          }
        ]
      },
      "post": {
        "description": "Đẩy đơn sang hãng vận chuyển qua một kết nối đã cấu hình trong shop.",
        "summary": "Tạo vận đơn cho một đơn",
        "tags": [],
        "parameters": [],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "orderId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Đơn hàng cần giao."
                  },
                  "connectionId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Kết nối hãng vận chuyển của shop dùng để giao."
                  },
                  "weightGram": {
                    "type": "integer",
                    "minimum": 0,
                    "exclusiveMinimum": 0,
                    "maximum": 50000,
                    "description": "Khối lượng khai với hãng, gram, từ 1 tới 50000."
                  },
                  "codAmount": {
                    "type": "string",
                    "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
                    "description": "Tiền thu hộ. Mặc định theo đơn."
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 5000,
                    "description": "Ghi chú cho hãng."
                  }
                },
                "required": [
                  "orderId",
                  "connectionId"
                ],
                "description": "Thân tạo vận đơn cho một đơn."
              },
              "example": {
                "orderId": "018f3b8e-1c2d-7a4b-9c3d-000000000101",
                "connectionId": "018f3b8e-1c2d-7a4b-9c3d-000000001301"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Một vận đơn kèm hành trình.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Shipment"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000001001",
                  "carrier": "ghn",
                  "trackingCode": "GHNABC123",
                  "status": "delivering",
                  "orderId": "018f3b8e-1c2d-7a4b-9c3d-000000000101",
                  "orderCode": "DH1024",
                  "codAmount": "480000",
                  "feeTotal": "30000",
                  "createdAt": "2026-10-02T03:15:00.000Z",
                  "expectedDeliveryAt": null,
                  "failReason": null,
                  "recipientName": "Nguyễn Văn An",
                  "recipientPhone": "+84901234567",
                  "recipientAddress": "12 Lê Lợi, Thạch Thang, Hải Châu, Đà Nẵng",
                  "weightGram": 500,
                  "pickedUpAt": "2026-10-02T03:15:00.000Z",
                  "events": []
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Dữ liệu hợp lệ nhưng nghiệp vụ không cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Hãng vận chuyển hiện không với tới được: thử lại sau.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "createShipment",
        "x-required-permissions": {
          "anyOf": [
            "pos.shipping.read"
          ],
          "allOf": [
            "pos.orders.update",
            "pos.products.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "conflict",
            "status": 409,
            "title": "Thao tác xung đột với trạng thái hiện tại"
          },
          {
            "code": "order-status-not-allowed",
            "status": 422,
            "title": "Không chuyển được đơn sang trạng thái này từ trạng thái hiện tại"
          },
          {
            "code": "insufficient-stock",
            "status": 422,
            "title": "Không đủ tồn kho cho thao tác này"
          },
          {
            "code": "carrier-unavailable",
            "status": 503,
            "title": "Hãng vận chuyển hiện không với tới được, hãy thử lại sau"
          }
        ]
      }
    },
    "/api/open/v1/shipments/{id}": {
      "get": {
        "description": "Trả vận đơn kèm hành trình theo thứ tự thời gian.",
        "summary": "Chi tiết một vận đơn",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "responses": {
          "200": {
            "description": "Một vận đơn kèm hành trình.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Shipment"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000001001",
                  "carrier": "ghn",
                  "trackingCode": "GHNABC123",
                  "status": "delivering",
                  "orderId": "018f3b8e-1c2d-7a4b-9c3d-000000000101",
                  "orderCode": "DH1024",
                  "codAmount": "480000",
                  "feeTotal": "30000",
                  "createdAt": "2026-10-02T03:15:00.000Z",
                  "expectedDeliveryAt": null,
                  "failReason": null,
                  "recipientName": "Nguyễn Văn An",
                  "recipientPhone": "+84901234567",
                  "recipientAddress": "12 Lê Lợi, Thạch Thang, Hải Châu, Đà Nẵng",
                  "weightGram": 500,
                  "pickedUpAt": "2026-10-02T03:15:00.000Z",
                  "events": []
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "getShipment",
        "x-required-permissions": {
          "anyOf": [
            "pos.shipping.read"
          ],
          "allOf": [
            "pos.orders.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      }
    },
    "/api/open/v1/shipments/{id}/cancel": {
      "post": {
        "description": "Huỷ vận đơn chưa kết thúc. Vận đơn đã giao hay đã hoàn không huỷ được.",
        "summary": "Huỷ vận đơn",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Lý do huỷ, lưu vào nhật ký."
                  }
                },
                "description": "Thân huỷ vận đơn."
              },
              "example": {
                "reason": "Khách đổi ý"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Một vận đơn kèm hành trình.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Shipment"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000001001",
                  "carrier": "ghn",
                  "trackingCode": "GHNABC123",
                  "status": "delivering",
                  "orderId": "018f3b8e-1c2d-7a4b-9c3d-000000000101",
                  "orderCode": "DH1024",
                  "codAmount": "480000",
                  "feeTotal": "30000",
                  "createdAt": "2026-10-02T03:15:00.000Z",
                  "expectedDeliveryAt": null,
                  "failReason": null,
                  "recipientName": "Nguyễn Văn An",
                  "recipientPhone": "+84901234567",
                  "recipientAddress": "12 Lê Lợi, Thạch Thang, Hải Châu, Đà Nẵng",
                  "weightGram": 500,
                  "pickedUpAt": "2026-10-02T03:15:00.000Z",
                  "events": []
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Hãng vận chuyển hiện không với tới được: thử lại sau.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "cancelShipment",
        "x-required-permissions": {
          "anyOf": [
            "pos.shipping.read"
          ],
          "allOf": [
            "pos.orders.update"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "shipment-not-cancellable",
            "status": 409,
            "title": "Vận đơn không còn huỷ được"
          },
          {
            "code": "carrier-unavailable",
            "status": 503,
            "title": "Hãng vận chuyển hiện không với tới được, hãy thử lại sau"
          }
        ]
      }
    },
    "/api/open/v1/pages": {
      "get": {
        "description": "Chỉ liệt kê những trang mà ứng dụng kết nối được chọn lúc tạo.",
        "summary": "Các trang chat được dùng",
        "tags": [],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Danh sách trang.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Page"
                      },
                      "description": "Các trang."
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "description": "Danh sách trang."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000001301",
                      "providerPageId": "1090000000000001",
                      "provider": "facebook_page",
                      "pageName": "Shop Mẫu",
                      "avatarUrl": null,
                      "status": "active"
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listPages",
        "x-required-permissions": {
          "anyOf": [
            "social.pages.read"
          ],
          "allOf": []
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          }
        ]
      }
    },
    "/api/open/v1/conversations": {
      "get": {
        "description": "Phân trang theo con trỏ, hội thoại có tin mới nhất trước. Lọc theo trang bằng `pageId` (mã trang trong DANIX, `id` của `GET /pages`); trang ngoài danh sách của ứng dụng bị từ chối `not-found`. Hội thoại chưa có tin nào không được liệt kê (vẫn đọc được theo id).",
        "summary": "Liệt kê hội thoại",
        "tags": [],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Số dòng mỗi trang, từ 1 đến 100. Mặc định 50.",
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "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.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Chỉ lấy hội thoại có `lastMessageAt` từ thời điểm này (ISO 8601 UTC), tính cả mốc. `lastMessageAt` là giờ của tin mới nhất theo kênh (Facebook, Zalo), không phải mốc sửa: tin tới muộn hay lịch sử đồng bộ về sau có thể mang giờ trước mốc đã đọc, còn đổi thẻ, đánh dấu đã đọc hay đổi tên khách không làm hội thoại hiện lại. Không dùng để đồng bộ tăng dần.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?!0000-)"
            }
          },
          {
            "name": "pageId",
            "in": "query",
            "description": "Chỉ lấy hội thoại của trang này: mã trang trong DANIX (`id` của `GET /pages`).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "unread",
            "in": "query",
            "description": "`true` chỉ lấy hội thoại còn tin chưa đọc.",
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          },
          {
            "name": "tagId",
            "in": "query",
            "description": "Chỉ lấy hội thoại có thẻ này.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Một trang hội thoại.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Conversation"
                      },
                      "description": "Các bản ghi của trang này."
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Con trỏ của trang kế; `null` khi đã hết dữ liệu.",
                      "nullable": true
                    }
                  },
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "description": "Một trang hội thoại."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000001201",
                      "pageId": "018f3b8e-1c2d-7a4b-9c3d-000000001301",
                      "providerPageId": "1090000000000001",
                      "provider": "facebook_page",
                      "type": "INBOX",
                      "customerName": "Nguyễn Văn An",
                      "customerAvatarUrl": null,
                      "unreadCount": 1,
                      "lastMessageText": "Shop ơi áo này còn size M không?",
                      "lastMessageAt": "2026-10-02T03:15:00.000Z",
                      "lastMessageBy": "customer",
                      "tags": [],
                      "createdAt": "2026-10-01T03:15:00.000Z"
                    }
                  ],
                  "nextCursor": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listConversations",
        "x-required-permissions": {
          "anyOf": [
            "social.conversations.read",
            "social.conversations.reply"
          ],
          "allOf": [
            "social.pages.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "invalid-cursor",
            "status": 400,
            "title": "Con trỏ phân trang không hợp lệ"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      }
    },
    "/api/open/v1/conversations/{id}": {
      "get": {
        "description": "Trả hội thoại kèm thẻ và số tin chưa đọc.",
        "summary": "Chi tiết một hội thoại",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "responses": {
          "200": {
            "description": "Một hội thoại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Conversation"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000001201",
                  "pageId": "018f3b8e-1c2d-7a4b-9c3d-000000001301",
                  "providerPageId": "1090000000000001",
                  "provider": "facebook_page",
                  "type": "INBOX",
                  "customerName": "Nguyễn Văn An",
                  "customerAvatarUrl": null,
                  "unreadCount": 0,
                  "lastMessageText": "Dạ còn ạ",
                  "lastMessageAt": "2026-10-02T03:16:00.000Z",
                  "lastMessageBy": "page",
                  "tags": [],
                  "createdAt": "2026-10-01T03:15:00.000Z"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "getConversation",
        "x-required-permissions": {
          "anyOf": [
            "social.conversations.read",
            "social.conversations.reply"
          ],
          "allOf": [
            "social.pages.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      }
    },
    "/api/open/v1/conversations/{id}/read": {
      "post": {
        "description": "Đặt số tin chưa đọc về 0.",
        "summary": "Đánh dấu hội thoại đã đọc",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "responses": {
          "200": {
            "description": "Thao tác thành công, không có dữ liệu trả về.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Luôn `true` khi thao tác thành công."
                    }
                  },
                  "required": [
                    "ok"
                  ],
                  "description": "Thao tác thành công, không có dữ liệu trả về."
                },
                "example": {
                  "ok": true
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "markConversationRead",
        "x-required-permissions": {
          "anyOf": [
            "social.conversations.read",
            "social.conversations.reply"
          ],
          "allOf": [
            "social.pages.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      }
    },
    "/api/open/v1/conversations/{id}/messages": {
      "get": {
        "description": "Phân trang theo con trỏ, tin mới nhất trước.",
        "summary": "Tin nhắn của hội thoại",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Con trỏ lấy từ `nextCursor` của trang trước.",
            "schema": {
              "type": "string",
              "maxLength": 512
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Số tin mỗi trang, 1 đến 100.",
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Một trang tin nhắn.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Message"
                      },
                      "description": "Các bản ghi của trang này."
                    },
                    "nextCursor": {
                      "type": "string",
                      "description": "Con trỏ của trang kế; `null` khi đã hết dữ liệu.",
                      "nullable": true
                    }
                  },
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "description": "Một trang tin nhắn."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000001101",
                      "conversationId": "018f3b8e-1c2d-7a4b-9c3d-000000001201",
                      "pageId": "018f3b8e-1c2d-7a4b-9c3d-000000001301",
                      "providerPageId": "1090000000000001",
                      "provider": "facebook_page",
                      "direction": "outbound",
                      "type": "text",
                      "text": "Dạ còn ạ, anh chị cần mấy cái ạ?",
                      "status": "sent",
                      "isDeleted": false,
                      "sentByName": "Trần Thị Bình",
                      "sentByIsIntegration": false,
                      "customerName": "Nguyễn Văn An",
                      "attachments": [],
                      "createdAt": "2026-10-02T03:15:00.000Z"
                    }
                  ],
                  "nextCursor": null
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listMessages",
        "x-required-permissions": {
          "anyOf": [
            "social.conversations.read",
            "social.conversations.reply"
          ],
          "allOf": [
            "social.pages.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "invalid-cursor",
            "status": 400,
            "title": "Con trỏ phân trang không hợp lệ"
          }
        ]
      },
      "post": {
        "description": "Gửi một tin nhắn chữ trong hội thoại. Gửi kèm `Idempotency-Key`: cùng khoá trong CÙNG hội thoại không gửi hai lần (cùng khoá ở hội thoại khác là một tin mới). Ngoài khung thời gian cho phép của kênh trả `messaging-window-closed`.",
        "summary": "Gửi tin nhắn",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255,
              "description": "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`."
            }
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000,
                    "description": "Nội dung tin nhắn."
                  },
                  "replyToMessageId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Trả lời một tin có sẵn của hội thoại."
                  }
                },
                "required": [
                  "text"
                ],
                "description": "Thân gửi tin nhắn. Nên kèm header `Idempotency-Key` để gửi lại an toàn."
              },
              "example": {
                "text": "Dạ còn ạ, anh chị cần mấy cái ạ?"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Một tin nhắn hoặc bình luận.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000001101",
                  "conversationId": "018f3b8e-1c2d-7a4b-9c3d-000000001201",
                  "pageId": "018f3b8e-1c2d-7a4b-9c3d-000000001301",
                  "providerPageId": "1090000000000001",
                  "provider": "facebook_page",
                  "direction": "outbound",
                  "type": "text",
                  "text": "Dạ còn ạ, anh chị cần mấy cái ạ?",
                  "status": "sent",
                  "isDeleted": false,
                  "sentByName": "Trần Thị Bình",
                  "sentByIsIntegration": false,
                  "customerName": "Nguyễn Văn An",
                  "attachments": [],
                  "createdAt": "2026-10-02T03:15:00.000Z"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              },
              "Idempotent-Replayed": {
                "description": "`true` khi đây là kết quả của lần gửi trước với cùng `Idempotency-Key`.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Dữ liệu hợp lệ nhưng nghiệp vụ không cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Hãng vận chuyển hiện không với tới được: thử lại sau.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "sendMessage",
        "x-required-permissions": {
          "anyOf": [
            "social.conversations.reply"
          ],
          "allOf": [
            "social.pages.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "idempotency-key-in-progress",
            "status": 409,
            "title": "Yêu cầu với cùng Idempotency-Key đang được xử lý"
          },
          {
            "code": "idempotency-key-reused",
            "status": 422,
            "title": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác"
          },
          {
            "code": "messaging-window-closed",
            "status": 422,
            "title": "Đã quá khung thời gian được phép nhắn cho khách này"
          },
          {
            "code": "page-not-connected",
            "status": 422,
            "title": "Trang chat không còn kết nối"
          },
          {
            "code": "conversation-not-replyable",
            "status": 422,
            "title": "Hội thoại này không trả lời được"
          },
          {
            "code": "channel-unavailable",
            "status": 503,
            "title": "Kênh chat tạm thời không phản hồi, hãy thử lại sau"
          }
        ],
        "x-idempotent": true
      }
    },
    "/api/open/v1/tags": {
      "get": {
        "description": "Thẻ hội thoại của các trang ứng dụng được dùng. Thẻ là của từng trang (`pageId`): chỉ gắn được vào hội thoại cùng trang.",
        "summary": "Các thẻ hội thoại",
        "tags": [],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Danh sách thẻ.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ChatTag"
                      },
                      "description": "Các thẻ."
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "description": "Danh sách thẻ."
                },
                "example": {
                  "data": [
                    {
                      "id": "018f3b8e-1c2d-7a4b-9c3d-000000001401",
                      "name": "Khách quen",
                      "color": "blue",
                      "pageId": "018f3b8e-1c2d-7a4b-9c3d-000000001301"
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "listChatTags",
        "x-required-permissions": {
          "anyOf": [
            "social.conversations.read",
            "social.conversations.reply"
          ],
          "allOf": [
            "social.pages.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          }
        ]
      }
    },
    "/api/open/v1/conversations/{id}/tags": {
      "post": {
        "description": "Gắn một thẻ có sẵn vào hội thoại. Gắn lại thẻ đã có là thành công.",
        "summary": "Gắn thẻ vào hội thoại",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tagId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Mã thẻ cần gắn."
                  }
                },
                "required": [
                  "tagId"
                ],
                "description": "Thân gắn thẻ vào hội thoại."
              },
              "example": {
                "tagId": "018f3b8e-1c2d-7a4b-9c3d-000000001401"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Thao tác thành công, không có dữ liệu trả về.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Luôn `true` khi thao tác thành công."
                    }
                  },
                  "required": [
                    "ok"
                  ],
                  "description": "Thao tác thành công, không có dữ liệu trả về."
                },
                "example": {
                  "ok": true
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "addConversationTag",
        "x-required-permissions": {
          "anyOf": [
            "social.conversations.reply"
          ],
          "allOf": [
            "social.pages.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      }
    },
    "/api/open/v1/conversations/{id}/tags/{tagId}": {
      "delete": {
        "description": "Gỡ một thẻ khỏi hội thoại.",
        "summary": "Gỡ thẻ khỏi hội thoại",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          },
          {
            "name": "tagId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã thẻ."
          }
        ],
        "responses": {
          "200": {
            "description": "Thao tác thành công, không có dữ liệu trả về.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Luôn `true` khi thao tác thành công."
                    }
                  },
                  "required": [
                    "ok"
                  ],
                  "description": "Thao tác thành công, không có dữ liệu trả về."
                },
                "example": {
                  "ok": true
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "removeConversationTag",
        "x-required-permissions": {
          "anyOf": [
            "social.conversations.reply"
          ],
          "allOf": [
            "social.pages.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          }
        ]
      }
    },
    "/api/open/v1/comments/{id}/hidden": {
      "post": {
        "description": "Chỉ áp dụng cho bình luận (`type = COMMENT`) của trang Facebook.",
        "summary": "Ẩn hoặc hiện một bình luận",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hidden": {
                    "type": "boolean",
                    "description": "`true` ẩn bình luận, `false` hiện lại."
                  }
                },
                "required": [
                  "hidden"
                ],
                "description": "Thân ẩn hoặc hiện bình luận."
              },
              "example": {
                "hidden": true
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Thao tác thành công, không có dữ liệu trả về.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Luôn `true` khi thao tác thành công."
                    }
                  },
                  "required": [
                    "ok"
                  ],
                  "description": "Thao tác thành công, không có dữ liệu trả về."
                },
                "example": {
                  "ok": true
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Dữ liệu hợp lệ nhưng nghiệp vụ không cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Hãng vận chuyển hiện không với tới được: thử lại sau.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "setCommentHidden",
        "x-required-permissions": {
          "anyOf": [
            "social.conversations.reply"
          ],
          "allOf": [
            "social.pages.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "page-not-connected",
            "status": 422,
            "title": "Trang chat không còn kết nối"
          },
          {
            "code": "conversation-not-replyable",
            "status": 422,
            "title": "Hội thoại này không trả lời được"
          },
          {
            "code": "channel-unavailable",
            "status": 503,
            "title": "Kênh chat tạm thời không phản hồi, hãy thử lại sau"
          }
        ]
      }
    },
    "/api/open/v1/comments/{id}/private-reply": {
      "post": {
        "description": "Gửi một tin nhắn riêng đáp lại bình luận. Mỗi bình luận chỉ nhắn riêng được một lần.",
        "summary": "Nhắn riêng cho người bình luận",
        "tags": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Mã định danh (UUID) của tài nguyên."
          }
        ],
        "requestBody": {
          "description": "Thân yêu cầu (JSON)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000,
                    "description": "Nội dung tin nhắn riêng gửi cho người bình luận."
                  }
                },
                "required": [
                  "text"
                ],
                "description": "Thân nhắn riêng cho người bình luận."
              },
              "example": {
                "text": "Cảm ơn bạn đã quan tâm, shop nhắn riêng nhé"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Một tin nhắn hoặc bình luận.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "id": "018f3b8e-1c2d-7a4b-9c3d-000000001101",
                  "conversationId": "018f3b8e-1c2d-7a4b-9c3d-000000001201",
                  "pageId": "018f3b8e-1c2d-7a4b-9c3d-000000001301",
                  "providerPageId": "1090000000000001",
                  "provider": "facebook_page",
                  "direction": "outbound",
                  "type": "text",
                  "text": "Dạ còn ạ, anh chị cần mấy cái ạ?",
                  "status": "sent",
                  "isDeleted": false,
                  "sentByName": "Trần Thị Bình",
                  "sentByIsIntegration": false,
                  "customerName": "Nguyễn Văn An",
                  "attachments": [],
                  "createdAt": "2026-10-02T03:15:00.000Z"
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": {
                "description": "Hạn mức số yêu cầu trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Số yêu cầu còn lại trong cửa sổ hiện tại.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "description": "Số giây còn lại tới khi cửa sổ làm mới.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "Dữ liệu gửi lên không hợp lệ (xem `code` và `errors`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá API, khoá sai hoặc đã bị thu hồi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Gói cước của shop hết hạn: khoá chỉ còn quyền đọc.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Khoá không có quyền thực hiện thao tác, shop bị đình chỉ hoặc chưa bật Tự động hoá.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Không tìm thấy tài nguyên (hoặc khoá không được thấy nó).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Xung đột với trạng thái hiện tại, hoặc Idempotency-Key đang được xử lý.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Thân yêu cầu vượt quá dung lượng cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Thân yêu cầu không phải JSON: gửi `Content-Type: application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Dữ liệu hợp lệ nhưng nghiệp vụ không cho phép.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Vượt hạn mức gọi API: chờ theo header `Retry-After` rồi thử lại.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Số giây phải chờ trước khi thử lại.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Lỗi hệ thống.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Hãng vận chuyển hiện không với tới được: thử lại sau.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "operationId": "sendPrivateReply",
        "x-required-permissions": {
          "anyOf": [
            "social.conversations.reply"
          ],
          "allOf": [
            "social.pages.read"
          ]
        },
        "x-error-codes": [
          {
            "code": "invalid-api-key",
            "status": 401,
            "title": "Khoá API thiếu, sai hoặc đã bị thu hồi"
          },
          {
            "code": "insufficient-permission",
            "status": 403,
            "title": "Khoá API không có quyền thực hiện thao tác này"
          },
          {
            "code": "shop-suspended",
            "status": 403,
            "title": "Shop đang bị đình chỉ"
          },
          {
            "code": "automation-not-active",
            "status": 403,
            "title": "Shop chưa bật tính năng Tự động hoá"
          },
          {
            "code": "rate-limited",
            "status": 429,
            "title": "Vượt hạn mức gọi API, hãy chờ rồi thử lại"
          },
          {
            "code": "internal-error",
            "status": 500,
            "title": "Lỗi hệ thống"
          },
          {
            "code": "subscription-expired",
            "status": 402,
            "title": "Gói cước của shop đã hết hạn, chỉ còn quyền đọc"
          },
          {
            "code": "validation-failed",
            "status": 400,
            "title": "Dữ liệu gửi lên không hợp lệ"
          },
          {
            "code": "payload-too-large",
            "status": 413,
            "title": "Thân yêu cầu vượt quá dung lượng cho phép"
          },
          {
            "code": "unsupported-media-type",
            "status": 415,
            "title": "Thân yêu cầu phải là JSON (`Content-Type: application/json`)"
          },
          {
            "code": "not-found",
            "status": 404,
            "title": "Không tìm thấy tài nguyên"
          },
          {
            "code": "conflict",
            "status": 409,
            "title": "Thao tác xung đột với trạng thái hiện tại"
          },
          {
            "code": "messaging-window-closed",
            "status": 422,
            "title": "Đã quá khung thời gian được phép nhắn cho khách này"
          },
          {
            "code": "page-not-connected",
            "status": 422,
            "title": "Trang chat không còn kết nối"
          },
          {
            "code": "conversation-not-replyable",
            "status": 422,
            "title": "Hội thoại này không trả lời được"
          },
          {
            "code": "channel-unavailable",
            "status": 503,
            "title": "Kênh chat tạm thời không phản hồi, hãy thử lại sau"
          }
        ]
      }
    }
  },
  "info": {
    "title": "DANIX Open API",
    "version": "1.0.0",
    "description": "API công khai của DANIX theo từng shop. Xác thực bằng khoá `dnx_live_…` trong header `Authorization: Bearer`. Tiền và số lượng là chuỗi thập phân; thời gian là ISO 8601 UTC; lỗi theo RFC 9457 kèm `code` ổn định."
  },
  "servers": [
    {
      "url": "https://danix.vn",
      "description": "Máy chủ chính"
    }
  ],
  "components": {
    "schemas": {
      "Shop": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã định danh của shop."
          },
          "slug": {
            "type": "string",
            "description": "Tên ngắn của shop trong đường dẫn."
          },
          "name": {
            "type": "string",
            "description": "Tên shop."
          },
          "application": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "description": "Tên ứng dụng kết nối đang gọi."
              },
              "permissions": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Các quyền khoá này được cấp, ví dụ `pos.orders.read`."
              },
              "pageIds": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "description": "Mã (UUID) trong DANIX của các trang chat khoá này được dùng — cùng giá trị `id` của `GET /pages`. Rỗng nếu khoá không có quyền chat."
              }
            },
            "required": [
              "name",
              "permissions",
              "pageIds"
            ],
            "description": "Thông tin ứng dụng kết nối gắn với khoá."
          },
          "readOnly": {
            "type": "boolean",
            "description": "`true` khi gói cước hết hạn: khoá chỉ đọc, ghi trả 402."
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "application",
          "readOnly"
        ],
        "description": "Shop của khoá API.",
        "title": "Shop"
      },
      "Problem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "Địa chỉ định danh loại lỗi (RFC 9457)."
          },
          "title": {
            "type": "string",
            "description": "Tiêu đề ngắn của lỗi, tiếng Việt."
          },
          "status": {
            "type": "integer",
            "description": "Mã trạng thái HTTP."
          },
          "detail": {
            "type": "string",
            "description": "Mô tả chi tiết của lần lỗi này."
          },
          "code": {
            "type": "string",
            "description": "Mã lỗi ổn định, kebab-case. Lập trình theo trường này."
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string",
                  "description": "Đường tới trường sai, ví dụ `lines.0.quantity`."
                },
                "message": {
                  "type": "string",
                  "description": "Vì sao trường đó sai."
                }
              },
              "required": [
                "path",
                "message"
              ]
            },
            "description": "Chỉ có khi `code` là `validation-failed`: danh sách trường sai."
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code"
        ],
        "description": "Lỗi theo RFC 9457 kèm mã ổn định.",
        "title": "Problem"
      },
      "GeoUnit": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã (UUID) của đơn vị trong danh mục của DANIX."
          },
          "system": {
            "type": "string",
            "enum": [
              "old",
              "new"
            ],
            "description": "Hệ địa giới: `old` (tỉnh, huyện, xã — trước 01/07/2025) hoặc `new` (tỉnh, xã)."
          },
          "level": {
            "type": "integer",
            "description": "Cấp: 1 tỉnh hoặc thành phố, 2 quận hoặc huyện (chỉ hệ `old`), 3 phường hoặc xã."
          },
          "parentId": {
            "type": "string",
            "format": "uuid",
            "description": "Đơn vị cấp trên; `null` với cấp tỉnh.",
            "nullable": true
          },
          "name": {
            "type": "string",
            "description": "Tên đơn vị."
          }
        },
        "required": [
          "id",
          "system",
          "level",
          "parentId",
          "name"
        ],
        "description": "Một đơn vị hành chính.",
        "title": "GeoUnit"
      },
      "OrderAddress": {
        "type": "object",
        "properties": {
          "recipientName": {
            "type": "string",
            "description": "Tên người nhận.",
            "nullable": true
          },
          "recipientPhone": {
            "type": "string",
            "description": "Số điện thoại người nhận.",
            "nullable": true
          },
          "addressLine": {
            "type": "string",
            "description": "Số nhà, đường.",
            "nullable": true
          },
          "geoSystem": {
            "type": "string",
            "enum": [
              "old",
              "new"
            ],
            "description": "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ữ.",
            "nullable": true
          },
          "provinceUnitId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã (UUID) tỉnh hoặc thành phố; `null` khi không có.",
            "nullable": true
          },
          "districtUnitId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã (UUID) quận hoặc huyện; hệ `new` luôn `null`.",
            "nullable": true
          },
          "wardUnitId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã (UUID) phường hoặc xã; `null` khi không có.",
            "nullable": true
          },
          "provinceName": {
            "type": "string",
            "description": "Tỉnh hoặc thành phố.",
            "nullable": true
          },
          "districtName": {
            "type": "string",
            "description": "Quận hoặc huyện.",
            "nullable": true
          },
          "wardName": {
            "type": "string",
            "description": "Phường hoặc xã.",
            "nullable": true
          }
        },
        "required": [
          "recipientName",
          "recipientPhone",
          "addressLine",
          "geoSystem",
          "provinceUnitId",
          "districtUnitId",
          "wardUnitId",
          "provinceName",
          "districtName",
          "wardName"
        ],
        "description": "Địa chỉ giao hàng. Chỉ có ở chi tiết đơn, không có trong danh sách.",
        "title": "OrderAddress"
      },
      "OrderTag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã thẻ."
          },
          "name": {
            "type": "string",
            "description": "Tên thẻ."
          }
        },
        "required": [
          "id",
          "name"
        ],
        "description": "Một thẻ gắn vào đơn.",
        "title": "OrderTag"
      },
      "OrderLine": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã dòng hàng."
          },
          "variantId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã mẫu mã."
          },
          "productId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã sản phẩm của mẫu mã.",
            "nullable": true
          },
          "variantCode": {
            "type": "string",
            "description": "Mã mẫu mã. Vắng khi khoá thiếu `pos.products.read`.",
            "nullable": true
          },
          "variantName": {
            "type": "string",
            "description": "Tên mẫu mã. Vắng khi khoá thiếu `pos.products.read`.",
            "nullable": true
          },
          "productName": {
            "type": "string",
            "description": "Tên sản phẩm. Vắng khi khoá thiếu `pos.products.read`.",
            "nullable": true
          },
          "quantity": {
            "type": "string",
            "pattern": "^\\d{1,9}(\\.\\d{1,3})?$",
            "description": "Số lượng. Chuỗi thập phân, tối đa ba chữ số lẻ."
          },
          "unitPrice": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Đơn giá. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "discount": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Giảm giá của dòng. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "lineTotal": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Thành tiền của dòng, đã trừ giảm giá. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "returnedQuantity": {
            "type": "string",
            "pattern": "^\\d{1,9}(\\.\\d{1,3})?$",
            "description": "Số lượng khách đã hoàn. Chuỗi thập phân, tối đa ba chữ số lẻ."
          }
        },
        "required": [
          "id",
          "variantId",
          "productId",
          "quantity",
          "unitPrice",
          "discount",
          "lineTotal",
          "returnedQuantity"
        ],
        "description": "Một dòng hàng của đơn.",
        "title": "OrderLine"
      },
      "OrderPayment": {
        "type": "object",
        "properties": {
          "methodId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã phương thức thanh toán."
          },
          "methodName": {
            "type": "string",
            "description": "Tên phương thức thanh toán."
          },
          "amount": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Số tiền thu theo phương thức này. Chuỗi thập phân, ví dụ \"150000\"."
          }
        },
        "required": [
          "methodId",
          "methodName",
          "amount"
        ],
        "description": "Một khoản thu của đơn.",
        "title": "OrderPayment"
      },
      "Order": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã định danh của đơn."
          },
          "code": {
            "type": "string",
            "description": "Mã đơn, duy nhất trong shop."
          },
          "status": {
            "type": "string",
            "enum": [
              "new",
              "waiting_stock",
              "confirmed",
              "packing",
              "ready_to_ship",
              "shipped",
              "delivered",
              "paid",
              "returning",
              "partially_returned",
              "returned",
              "cancelled",
              "deleted"
            ],
            "description": "Trạng thái đơn."
          },
          "warehouseId": {
            "type": "string",
            "format": "uuid",
            "description": "Kho xuất hàng."
          },
          "warehouseName": {
            "type": "string",
            "description": "Tên kho xuất hàng.",
            "nullable": true
          },
          "customerId": {
            "type": "string",
            "format": "uuid",
            "description": "Khách hàng gắn với đơn.",
            "nullable": true
          },
          "salesChannelId": {
            "type": "string",
            "format": "uuid",
            "description": "Kênh bán.",
            "nullable": true
          },
          "salesChannelName": {
            "type": "string",
            "description": "Tên kênh bán.",
            "nullable": true
          },
          "receivedAtShop": {
            "type": "boolean",
            "description": "Khách nhận hàng tại shop."
          },
          "handlerUserId": {
            "type": "string",
            "format": "uuid",
            "description": "Người phụ trách đơn. `null` khi đơn do Tự động tạo đơn sinh đang chờ chia (chưa giao được cho ai); đơn tạo qua API luôn có người phụ trách.",
            "nullable": true
          },
          "handlerName": {
            "type": "string",
            "description": "Tên người phụ trách đơn; người đã rời shop vẫn có tên.",
            "nullable": true
          },
          "billFullName": {
            "type": "string",
            "description": "Tên người mua ghi trên đơn.",
            "nullable": true
          },
          "billPhone": {
            "type": "string",
            "description": "Số điện thoại người mua.",
            "nullable": true
          },
          "billEmail": {
            "type": "string",
            "description": "Email người mua.",
            "nullable": true
          },
          "shippingAddress": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/OrderAddress"
              }
            ]
          },
          "totalPrice": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Tiền hàng. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "discount": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Giảm giá cả đơn. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "shippingFee": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Phí vận chuyển khách trả. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "freeShipping": {
            "type": "boolean",
            "description": "Shop chịu phí giao hàng."
          },
          "surcharge": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Phụ thu. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "tax": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Thuế. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "totalAmount": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Tổng tiền đơn. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "returnedAmount": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Tiền hàng khách đã hoàn. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "paidAmount": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Đã thanh toán. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "creditApplied": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Tiền cấn trừ vào đơn này từ phiếu đổi trả (khách trả hàng của một đơn khác): khách đã trả cho đơn này bằng khoản ấy, nên số còn phải thu đã trừ nó. Chỉ có ở chi tiết đơn, không có trong danh sách. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "exchangeReturn": {
            "type": "boolean",
            "description": "Đơn trả để ĐỔI hàng: mọi lượt khách trả hàng của đơn đều là đổi (đổi size, đổi màu…), nên đơn không tính là hoàn — không vào tỉ lệ hoàn của số điện thoại, không gửi tin hoàn hay sự kiện hoàn sang Facebook. Chỉ có ở chi tiết đơn, không có trong danh sách."
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm phát sinh đơn. ISO 8601, múi giờ UTC."
          },
          "note": {
            "type": "string",
            "description": "Ghi chú nội bộ.",
            "nullable": true
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderTag"
            },
            "description": "Các thẻ của đơn."
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderLine"
            },
            "description": "Các dòng hàng. Chỉ có ở chi tiết đơn, không có trong danh sách."
          },
          "payments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderPayment"
            },
            "description": "Các khoản thu. Chỉ có ở chi tiết đơn, không có trong danh sách."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm tạo. ISO 8601, múi giờ UTC."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm sửa gần nhất. ISO 8601, múi giờ UTC."
          },
          "deletedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm xoá mềm; `null` nếu chưa xoá. ISO 8601 UTC hoặc `null`.",
            "nullable": true
          }
        },
        "required": [
          "id",
          "code",
          "status",
          "warehouseId",
          "warehouseName",
          "customerId",
          "salesChannelId",
          "salesChannelName",
          "receivedAtShop",
          "handlerUserId",
          "handlerName",
          "billFullName",
          "billPhone",
          "billEmail",
          "totalPrice",
          "discount",
          "shippingFee",
          "freeShipping",
          "surcharge",
          "tax",
          "totalAmount",
          "returnedAmount",
          "paidAmount",
          "occurredAt",
          "note",
          "tags",
          "createdAt",
          "updatedAt",
          "deletedAt"
        ],
        "description": "Một đơn hàng.",
        "title": "Order"
      },
      "Note": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã ghi chú."
          },
          "message": {
            "type": "string",
            "description": "Nội dung ghi chú."
          },
          "createdByName": {
            "type": "string",
            "description": "Tên người hay ứng dụng đã ghi.",
            "nullable": true
          },
          "createdByIsIntegration": {
            "type": "boolean",
            "description": "`true` khi ghi chú do một ứng dụng kết nối tạo."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm tạo. ISO 8601, múi giờ UTC."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm sửa gần nhất. ISO 8601, múi giờ UTC."
          }
        },
        "required": [
          "id",
          "message",
          "createdByName",
          "createdByIsIntegration",
          "createdAt",
          "updatedAt"
        ],
        "description": "Một ghi chú.",
        "title": "Note"
      },
      "Staff": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã người dùng — dùng làm `handlerUserId`."
          },
          "name": {
            "type": "string",
            "description": "Tên hiển thị của nhân viên."
          }
        },
        "required": [
          "id",
          "name"
        ],
        "description": "Một nhân viên đang làm việc trong shop.",
        "title": "Staff"
      },
      "Role": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã vai trò — dùng làm `handlerRoleId`."
          },
          "name": {
            "type": "string",
            "description": "Tên vai trò."
          }
        },
        "required": [
          "id",
          "name"
        ],
        "description": "Một vai trò của shop.",
        "title": "Role"
      },
      "ProductSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã sản phẩm."
          },
          "code": {
            "type": "string",
            "description": "Mã sản phẩm, duy nhất trong shop."
          },
          "name": {
            "type": "string",
            "description": "Tên sản phẩm."
          },
          "type": {
            "type": "string",
            "enum": [
              "simple",
              "service"
            ],
            "description": "Loại: `simple` (hàng hoá) hoặc `service` (dịch vụ)."
          },
          "isActive": {
            "type": "boolean",
            "description": "Sản phẩm còn bán."
          },
          "categoryNames": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tên các danh mục chứa sản phẩm."
          },
          "variantCount": {
            "type": "integer",
            "description": "Số mẫu mã."
          },
          "imageUrl": {
            "type": "string",
            "description": "Ảnh đại diện.",
            "nullable": true
          },
          "priceFrom": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Giá bán thấp nhất trong các mẫu mã. Chuỗi thập phân hoặc `null`.",
            "nullable": true
          },
          "priceTo": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Giá bán cao nhất trong các mẫu mã. Chuỗi thập phân hoặc `null`.",
            "nullable": true
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Lần sửa gần nhất. Danh sách sắp theo `(updatedAt, id)`; dùng làm mốc `updatedSince`. ISO 8601, múi giờ UTC."
          },
          "deletedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm xoá mềm; `null` nếu chưa xoá. Chỉ khác `null` khi gọi với `includeDeleted=true`. ISO 8601 UTC hoặc `null`.",
            "nullable": true
          }
        },
        "required": [
          "id",
          "code",
          "name",
          "type",
          "isActive",
          "categoryNames",
          "variantCount",
          "imageUrl",
          "priceFrom",
          "priceTo",
          "updatedAt",
          "deletedAt"
        ],
        "description": "Một dòng trong danh sách sản phẩm.",
        "title": "ProductSummary"
      },
      "Variant": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã mẫu mã."
          },
          "code": {
            "type": "string",
            "description": "Mã mẫu mã (SKU), duy nhất trong shop."
          },
          "barcode": {
            "type": "string",
            "description": "Mã vạch.",
            "nullable": true
          },
          "price": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Giá bán. Chuỗi thập phân, ví dụ \"150000\"."
          },
          "weightGrams": {
            "type": "string",
            "description": "Cân nặng, gram. Chuỗi thập phân.",
            "nullable": true
          },
          "lengthCm": {
            "type": "string",
            "description": "Chiều dài, cm.",
            "nullable": true
          },
          "widthCm": {
            "type": "string",
            "description": "Chiều rộng, cm.",
            "nullable": true
          },
          "heightCm": {
            "type": "string",
            "description": "Chiều cao, cm.",
            "nullable": true
          },
          "isActive": {
            "type": "boolean",
            "description": "Mẫu mã còn bán."
          },
          "attributes": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Thuộc tính của mẫu mã theo TÊN, ví dụ `{ \"Size\": \"M\", \"Màu\": \"Đỏ\" }`, theo thứ tự thuộc tính của sản phẩm. Rỗng với sản phẩm không có thuộc tính."
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Từ khoá chốt đơn — khách gõ trong tin nhắn / bình luận để tự động lên đơn mẫu mã này. Duy nhất trong shop, không phân biệt hoa thường. Khác `attributes`."
          }
        },
        "required": [
          "id",
          "code",
          "barcode",
          "price",
          "weightGrams",
          "lengthCm",
          "widthCm",
          "heightCm",
          "isActive",
          "attributes",
          "keywords"
        ],
        "description": "Một mẫu mã (biến thể) của sản phẩm.",
        "title": "Variant"
      },
      "ProductImage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã tệp."
          },
          "url": {
            "type": "string",
            "description": "Đường dẫn tuyệt đối tới tệp."
          },
          "kind": {
            "type": "string",
            "enum": [
              "image",
              "video"
            ],
            "description": "Loại tệp."
          },
          "variantIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Các mẫu mã dùng tệp này; rỗng là tệp chung."
          }
        },
        "required": [
          "id",
          "url",
          "kind",
          "variantIds"
        ],
        "description": "Ảnh hoặc video của sản phẩm.",
        "title": "ProductImage"
      },
      "Product": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã sản phẩm."
          },
          "code": {
            "type": "string",
            "description": "Mã sản phẩm, duy nhất trong shop."
          },
          "name": {
            "type": "string",
            "description": "Tên sản phẩm."
          },
          "description": {
            "type": "string",
            "description": "Mô tả.",
            "nullable": true
          },
          "type": {
            "type": "string",
            "enum": [
              "simple",
              "service"
            ],
            "description": "Loại: `simple` (hàng hoá) hoặc `service` (dịch vụ)."
          },
          "isActive": {
            "type": "boolean",
            "description": "Sản phẩm còn bán."
          },
          "categoryIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Các danh mục chứa sản phẩm."
          },
          "variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Variant"
            },
            "description": "Các mẫu mã."
          },
          "images": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductImage"
            },
            "description": "Ảnh và video."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm tạo. ISO 8601, múi giờ UTC."
          },
          "deletedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm xoá mềm; `null` nếu chưa xoá. ISO 8601 UTC hoặc `null`.",
            "nullable": true
          }
        },
        "required": [
          "id",
          "code",
          "name",
          "description",
          "type",
          "isActive",
          "categoryIds",
          "variants",
          "images",
          "createdAt",
          "deletedAt"
        ],
        "description": "Một sản phẩm kèm mẫu mã và ảnh.",
        "title": "Product"
      },
      "Category": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã danh mục."
          },
          "parentId": {
            "type": "string",
            "format": "uuid",
            "description": "Danh mục cha.",
            "nullable": true
          },
          "name": {
            "type": "string",
            "description": "Tên danh mục."
          },
          "slug": {
            "type": "string",
            "description": "Tên ngắn trong đường dẫn."
          }
        },
        "required": [
          "id",
          "parentId",
          "name",
          "slug"
        ],
        "description": "Một danh mục sản phẩm.",
        "title": "Category"
      },
      "Warehouse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã kho."
          },
          "code": {
            "type": "string",
            "description": "Mã kho."
          },
          "name": {
            "type": "string",
            "description": "Tên kho."
          },
          "phone": {
            "type": "string",
            "description": "Số điện thoại kho.",
            "nullable": true
          },
          "addressLine": {
            "type": "string",
            "description": "Địa chỉ kho.",
            "nullable": true
          },
          "provinceName": {
            "type": "string",
            "description": "Tỉnh hoặc thành phố.",
            "nullable": true
          },
          "districtName": {
            "type": "string",
            "description": "Quận hoặc huyện.",
            "nullable": true
          },
          "wardName": {
            "type": "string",
            "description": "Phường hoặc xã.",
            "nullable": true
          },
          "isDefault": {
            "type": "boolean",
            "description": "Kho mặc định của shop."
          }
        },
        "required": [
          "id",
          "code",
          "name",
          "phone",
          "addressLine",
          "provinceName",
          "districtName",
          "wardName",
          "isDefault"
        ],
        "description": "Một kho hàng.",
        "title": "Warehouse"
      },
      "StockRow": {
        "type": "object",
        "properties": {
          "variantId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã mẫu mã."
          },
          "productId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã sản phẩm."
          },
          "productName": {
            "type": "string",
            "description": "Tên sản phẩm."
          },
          "variantName": {
            "type": "string",
            "description": "Tên mẫu mã."
          },
          "code": {
            "type": "string",
            "description": "Mã mẫu mã (SKU)."
          },
          "barcode": {
            "type": "string",
            "description": "Mã vạch.",
            "nullable": true
          },
          "warehouseId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã kho."
          },
          "warehouseName": {
            "type": "string",
            "description": "Tên kho."
          },
          "quantity": {
            "type": "string",
            "pattern": "^\\d{1,9}(\\.\\d{1,3})?$",
            "description": "Tồn thực trong kho. Chuỗi thập phân, tối đa ba chữ số lẻ."
          },
          "inTransit": {
            "type": "string",
            "pattern": "^\\d{1,9}(\\.\\d{1,3})?$",
            "description": "Số lượng đang trên đường về kho. Chuỗi thập phân, tối đa ba chữ số lẻ."
          },
          "price": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Giá bán. Chuỗi thập phân, ví dụ \"150000\"."
          }
        },
        "required": [
          "variantId",
          "productId",
          "productName",
          "variantName",
          "code",
          "barcode",
          "warehouseId",
          "warehouseName",
          "quantity",
          "inTransit",
          "price"
        ],
        "description": "Tồn kho của một mẫu mã tại một kho.",
        "title": "StockRow"
      },
      "InventoryDocumentLine": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã dòng phiếu."
          },
          "variantId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã mẫu mã."
          },
          "variantCode": {
            "type": "string",
            "description": "Mã mẫu mã."
          },
          "quantity": {
            "type": "string",
            "description": "Số lượng nhập hoặc xuất. Chuỗi thập phân.",
            "nullable": true
          },
          "countedQuantity": {
            "type": "string",
            "description": "Số đếm thực tế (phiếu kiểm kho).",
            "nullable": true
          },
          "unitCost": {
            "type": "string",
            "nullable": true,
            "description": "Giá nhập. Chỉ có khi khoá được xem giá vốn."
          },
          "note": {
            "type": "string",
            "description": "Ghi chú dòng.",
            "nullable": true
          }
        },
        "required": [
          "id",
          "variantId",
          "variantCode",
          "quantity",
          "countedQuantity",
          "note"
        ],
        "description": "Một dòng của phiếu kho.",
        "title": "InventoryDocumentLine"
      },
      "InventoryDocument": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã phiếu."
          },
          "kind": {
            "type": "string",
            "description": "Loại phiếu: `receipt`, `issue`, `stocktake`…"
          },
          "code": {
            "type": "string",
            "description": "Mã phiếu."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "ordered",
              "posted",
              "in_transit",
              "completed",
              "cancelled"
            ],
            "description": "Trạng thái phiếu: `draft` là nháp, `posted` đã ghi sổ."
          },
          "warehouseId": {
            "type": "string",
            "format": "uuid",
            "description": "Kho của phiếu."
          },
          "supplierId": {
            "type": "string",
            "format": "uuid",
            "description": "Nhà cung cấp.",
            "nullable": true
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time",
            "description": "Ngày của phiếu. ISO 8601, múi giờ UTC."
          },
          "postedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm ghi sổ. ISO 8601 UTC hoặc `null`.",
            "nullable": true
          },
          "costingMethod": {
            "type": "string",
            "enum": [
              "average",
              "fifo"
            ],
            "description": "Phương pháp tính giá vốn khi ghi sổ.",
            "nullable": true
          },
          "note": {
            "type": "string",
            "description": "Ghi chú phiếu.",
            "nullable": true
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InventoryDocumentLine"
            },
            "description": "Các dòng của phiếu."
          }
        },
        "required": [
          "id",
          "kind",
          "code",
          "status",
          "warehouseId",
          "supplierId",
          "occurredAt",
          "postedAt",
          "costingMethod",
          "note",
          "lines"
        ],
        "description": "Một phiếu kho.",
        "title": "InventoryDocument"
      },
      "CustomerSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã khách hàng."
          },
          "name": {
            "type": "string",
            "description": "Tên khách."
          },
          "gender": {
            "type": "string",
            "enum": [
              "male",
              "female",
              "other"
            ],
            "description": "Giới tính.",
            "nullable": true
          },
          "dateOfBirth": {
            "type": "string",
            "description": "Ngày sinh, dạng YYYY-MM-DD.",
            "nullable": true
          },
          "source": {
            "type": "string",
            "enum": [
              "manual",
              "order",
              "import"
            ],
            "description": "Nguồn tạo hồ sơ: `manual`, `order` hoặc `import`."
          },
          "isBlocked": {
            "type": "boolean",
            "description": "Khách đang bị chặn."
          },
          "primaryPhone": {
            "type": "string",
            "description": "Số điện thoại chính, dạng +84xxxxxxxxx.",
            "nullable": true
          },
          "primaryEmail": {
            "type": "string",
            "description": "Email chính.",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm tạo. ISO 8601, múi giờ UTC."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm sửa gần nhất. ISO 8601, múi giờ UTC."
          },
          "deletedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm xoá mềm; `null` nếu chưa xoá. ISO 8601 UTC hoặc `null`.",
            "nullable": true
          }
        },
        "required": [
          "id",
          "name",
          "gender",
          "dateOfBirth",
          "source",
          "isBlocked",
          "primaryPhone",
          "primaryEmail",
          "createdAt",
          "updatedAt",
          "deletedAt"
        ],
        "description": "Một dòng trong danh sách khách hàng.",
        "title": "CustomerSummary"
      },
      "CustomerContact": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã liên hệ."
          },
          "kind": {
            "type": "string",
            "enum": [
              "phone",
              "email"
            ],
            "description": "Loại liên hệ."
          },
          "value": {
            "type": "string",
            "description": "Giá trị đã chuẩn hoá: số điện thoại dạng +84xxxxxxxxx, hoặc email."
          },
          "isPrimary": {
            "type": "boolean",
            "description": "Liên hệ chính của loại này."
          }
        },
        "required": [
          "id",
          "kind",
          "value",
          "isPrimary"
        ],
        "description": "Một số điện thoại hoặc email của khách.",
        "title": "CustomerContact"
      },
      "CustomerAddress": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã địa chỉ."
          },
          "recipientName": {
            "type": "string",
            "description": "Tên người nhận.",
            "nullable": true
          },
          "recipientPhone": {
            "type": "string",
            "description": "Số điện thoại người nhận.",
            "nullable": true
          },
          "addressLine": {
            "type": "string",
            "description": "Số nhà, đường."
          },
          "geoSystem": {
            "type": "string",
            "enum": [
              "old",
              "new"
            ],
            "description": "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ữ.",
            "nullable": true
          },
          "provinceUnitId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã (UUID) tỉnh hoặc thành phố; `null` khi không có.",
            "nullable": true
          },
          "districtUnitId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã (UUID) quận hoặc huyện; hệ `new` luôn `null`.",
            "nullable": true
          },
          "wardUnitId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã (UUID) phường hoặc xã; `null` khi không có.",
            "nullable": true
          },
          "provinceName": {
            "type": "string",
            "description": "Tỉnh hoặc thành phố.",
            "nullable": true
          },
          "districtName": {
            "type": "string",
            "description": "Quận hoặc huyện.",
            "nullable": true
          },
          "wardName": {
            "type": "string",
            "description": "Phường hoặc xã.",
            "nullable": true
          }
        },
        "required": [
          "id",
          "recipientName",
          "recipientPhone",
          "addressLine",
          "geoSystem",
          "provinceUnitId",
          "districtUnitId",
          "wardUnitId",
          "provinceName",
          "districtName",
          "wardName"
        ],
        "description": "Một địa chỉ giao hàng của khách.",
        "title": "CustomerAddress"
      },
      "Customer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã khách hàng."
          },
          "name": {
            "type": "string",
            "description": "Tên khách."
          },
          "gender": {
            "type": "string",
            "enum": [
              "male",
              "female",
              "other"
            ],
            "description": "Giới tính.",
            "nullable": true
          },
          "dateOfBirth": {
            "type": "string",
            "description": "Ngày sinh, dạng YYYY-MM-DD.",
            "nullable": true
          },
          "source": {
            "type": "string",
            "enum": [
              "manual",
              "order",
              "import"
            ],
            "description": "Nguồn tạo hồ sơ: `manual`, `order` hoặc `import`."
          },
          "isBlocked": {
            "type": "boolean",
            "description": "Khách đang bị chặn."
          },
          "primaryPhone": {
            "type": "string",
            "description": "Số điện thoại chính, dạng +84xxxxxxxxx.",
            "nullable": true
          },
          "primaryEmail": {
            "type": "string",
            "description": "Email chính.",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm tạo. ISO 8601, múi giờ UTC."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm sửa gần nhất. ISO 8601, múi giờ UTC."
          },
          "deletedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm xoá mềm; `null` nếu chưa xoá. ISO 8601 UTC hoặc `null`.",
            "nullable": true
          },
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomerContact"
            },
            "description": "Số điện thoại và email."
          },
          "addresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomerAddress"
            },
            "description": "Địa chỉ giao hàng, địa chỉ dùng gần nhất đứng đầu."
          }
        },
        "required": [
          "id",
          "name",
          "gender",
          "dateOfBirth",
          "source",
          "isBlocked",
          "primaryPhone",
          "primaryEmail",
          "createdAt",
          "updatedAt",
          "deletedAt",
          "contacts",
          "addresses"
        ],
        "description": "Một khách hàng kèm liên hệ và địa chỉ.",
        "title": "Customer"
      },
      "ShipmentSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã vận đơn."
          },
          "carrier": {
            "type": "string",
            "enum": [
              "ghn",
              "vtp"
            ],
            "description": "Hãng vận chuyển."
          },
          "trackingCode": {
            "type": "string",
            "description": "Mã vận đơn của hãng. Rỗng khi chưa đẩy sang hãng.",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivering",
              "delivered",
              "delivery_failed",
              "returning",
              "returned",
              "partially_returned",
              "cancelled",
              "lost",
              "damaged",
              "exception",
              "scrapped"
            ],
            "description": "Trạng thái chuẩn của vận đơn."
          },
          "orderId": {
            "type": "string",
            "format": "uuid",
            "description": "Đơn hàng của vận đơn."
          },
          "orderCode": {
            "type": "string",
            "description": "Mã đơn hàng."
          },
          "codAmount": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Tiền thu hộ (COD). Chuỗi thập phân hoặc `null`.",
            "nullable": true
          },
          "feeTotal": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Tổng phí vận chuyển. Chuỗi thập phân hoặc `null`.",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm tạo. ISO 8601, múi giờ UTC."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Lần sửa gần nhất. Danh sách sắp theo `(updatedAt, id)`; dùng làm mốc `updatedSince`. ISO 8601, múi giờ UTC."
          },
          "expectedDeliveryAt": {
            "type": "string",
            "format": "date-time",
            "description": "Dự kiến giao. ISO 8601 UTC hoặc `null`.",
            "nullable": true
          },
          "failReason": {
            "type": "string",
            "description": "Lý do giao thất bại, nếu có.",
            "nullable": true
          }
        },
        "required": [
          "id",
          "carrier",
          "trackingCode",
          "status",
          "orderId",
          "orderCode",
          "codAmount",
          "feeTotal",
          "createdAt",
          "updatedAt",
          "expectedDeliveryAt",
          "failReason"
        ],
        "description": "Một dòng trong danh sách vận đơn.",
        "title": "ShipmentSummary"
      },
      "ShipmentEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã sự kiện."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivering",
              "delivered",
              "delivery_failed",
              "returning",
              "returned",
              "partially_returned",
              "cancelled",
              "lost",
              "damaged",
              "exception",
              "scrapped"
            ],
            "description": "Trạng thái chuẩn sau sự kiện; `null` nếu sự kiện không đổi trạng thái.",
            "nullable": true
          },
          "carrierStatus": {
            "type": "string",
            "description": "Mã trạng thái gốc của hãng.",
            "nullable": true
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm hãng ghi nhận. ISO 8601, múi giờ UTC."
          },
          "location": {
            "type": "string",
            "description": "Nơi xảy ra.",
            "nullable": true
          },
          "description": {
            "type": "string",
            "description": "Mô tả của hãng.",
            "nullable": true
          },
          "reason": {
            "type": "string",
            "description": "Lý do (giao thất bại, hoàn…).",
            "nullable": true
          }
        },
        "required": [
          "id",
          "status",
          "carrierStatus",
          "occurredAt",
          "location",
          "description",
          "reason"
        ],
        "description": "Một mốc trong hành trình vận đơn.",
        "title": "ShipmentEvent"
      },
      "Shipment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã vận đơn."
          },
          "carrier": {
            "type": "string",
            "enum": [
              "ghn",
              "vtp"
            ],
            "description": "Hãng vận chuyển."
          },
          "trackingCode": {
            "type": "string",
            "description": "Mã vận đơn của hãng. Rỗng khi chưa đẩy sang hãng.",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivering",
              "delivered",
              "delivery_failed",
              "returning",
              "returned",
              "partially_returned",
              "cancelled",
              "lost",
              "damaged",
              "exception",
              "scrapped"
            ],
            "description": "Trạng thái chuẩn của vận đơn."
          },
          "orderId": {
            "type": "string",
            "format": "uuid",
            "description": "Đơn hàng của vận đơn."
          },
          "orderCode": {
            "type": "string",
            "description": "Mã đơn hàng."
          },
          "codAmount": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Tiền thu hộ (COD). Chuỗi thập phân hoặc `null`.",
            "nullable": true
          },
          "feeTotal": {
            "type": "string",
            "pattern": "^\\d{1,12}(\\.\\d{1,4})?$",
            "description": "Tổng phí vận chuyển. Chuỗi thập phân hoặc `null`.",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm tạo. ISO 8601, múi giờ UTC."
          },
          "expectedDeliveryAt": {
            "type": "string",
            "format": "date-time",
            "description": "Dự kiến giao. ISO 8601 UTC hoặc `null`.",
            "nullable": true
          },
          "failReason": {
            "type": "string",
            "description": "Lý do giao thất bại, nếu có.",
            "nullable": true
          },
          "recipientName": {
            "type": "string",
            "description": "Tên người nhận.",
            "nullable": true
          },
          "recipientPhone": {
            "type": "string",
            "description": "Số điện thoại người nhận.",
            "nullable": true
          },
          "recipientAddress": {
            "type": "string",
            "description": "Địa chỉ người nhận.",
            "nullable": true
          },
          "weightGram": {
            "type": "integer",
            "description": "Khối lượng khai với hãng, gram.",
            "nullable": true
          },
          "pickedUpAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm hãng lấy hàng. ISO 8601 UTC hoặc `null`.",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ShipmentEvent"
            },
            "description": "Hành trình, theo thứ tự thời gian."
          }
        },
        "required": [
          "id",
          "carrier",
          "trackingCode",
          "status",
          "orderId",
          "orderCode",
          "codAmount",
          "feeTotal",
          "createdAt",
          "expectedDeliveryAt",
          "failReason",
          "recipientName",
          "recipientPhone",
          "recipientAddress",
          "weightGram",
          "pickedUpAt",
          "events"
        ],
        "description": "Một vận đơn kèm hành trình.",
        "title": "Shipment"
      },
      "Page": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã trang trong DANIX. Đây là giá trị dùng làm `pageId` ở mọi chỗ khác của API."
          },
          "providerPageId": {
            "type": "string",
            "description": "Mã trang ở kênh gốc (Facebook, Zalo…). Chỉ để đối chiếu, không dùng làm tham số."
          },
          "provider": {
            "type": "string",
            "enum": [
              "facebook_page",
              "zalo_oa",
              "zalo_personal"
            ],
            "description": "Kênh: `facebook_page`, `zalo_oa` hoặc `zalo_personal`."
          },
          "pageName": {
            "type": "string",
            "description": "Tên trang."
          },
          "avatarUrl": {
            "type": "string",
            "description": "Ảnh đại diện của trang, lưu trên kho của DANIX. `null` khi chưa có bản lưu.",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "token_expired",
              "banned"
            ],
            "description": "Tình trạng kết nối: `active`, `token_expired` hoặc `banned`."
          }
        },
        "required": [
          "id",
          "providerPageId",
          "provider",
          "pageName",
          "avatarUrl",
          "status"
        ],
        "description": "Một trang chat mà khoá được dùng.",
        "title": "Page"
      },
      "Conversation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã hội thoại."
          },
          "pageId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã trang trong DANIX (`id` của `GET /pages`)."
          },
          "providerPageId": {
            "type": "string",
            "description": "Mã trang ở kênh gốc."
          },
          "provider": {
            "type": "string",
            "enum": [
              "facebook_page",
              "zalo_oa",
              "zalo_personal"
            ],
            "description": "Kênh của hội thoại."
          },
          "type": {
            "type": "string",
            "enum": [
              "INBOX",
              "COMMENT",
              "GROUP"
            ],
            "description": "Loại: `INBOX` tin nhắn, `COMMENT` bình luận, `GROUP` nhóm."
          },
          "customerName": {
            "type": "string",
            "description": "Tên khách ở kênh gốc.",
            "nullable": true
          },
          "customerAvatarUrl": {
            "type": "string",
            "description": "Ảnh đại diện của khách, lưu trên kho của DANIX. `null` khi chưa có bản lưu.",
            "nullable": true
          },
          "unreadCount": {
            "type": "integer",
            "description": "Số tin khách chưa được đọc."
          },
          "lastMessageText": {
            "type": "string",
            "description": "Nội dung tin gần nhất.",
            "nullable": true
          },
          "lastMessageAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm tin gần nhất. ISO 8601 UTC hoặc `null`.",
            "nullable": true
          },
          "lastMessageBy": {
            "type": "string",
            "enum": [
              "customer",
              "page"
            ],
            "description": "Ai gửi tin gần nhất.",
            "nullable": true
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "Mã thẻ."
                },
                "name": {
                  "type": "string",
                  "description": "Tên thẻ."
                }
              },
              "required": [
                "id",
                "name"
              ]
            },
            "description": "Các thẻ gắn vào hội thoại."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm tạo hội thoại. ISO 8601, múi giờ UTC."
          }
        },
        "required": [
          "id",
          "pageId",
          "providerPageId",
          "provider",
          "type",
          "customerName",
          "customerAvatarUrl",
          "unreadCount",
          "lastMessageText",
          "lastMessageAt",
          "lastMessageBy",
          "tags",
          "createdAt"
        ],
        "description": "Một hội thoại.",
        "title": "Conversation"
      },
      "MessageAttachment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã tệp đính kèm."
          },
          "type": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "audio",
              "file",
              "link",
              "button"
            ],
            "description": "Loại: bốn loại đầu là tệp; `link` và `button` là nút của tin mẫu."
          },
          "url": {
            "type": "string",
            "description": "Tệp: bản GỐC trên kho của DANIX, `null` khi chưa lưu về (xem `originalState`). Loại `link`: địa chỉ nút trỏ tới. Không bao giờ là đường dẫn của Facebook hay Zalo.",
            "nullable": true
          },
          "previewUrl": {
            "type": "string",
            "description": "Bản xem trước (ảnh thu nhỏ, ảnh bìa video) trên kho của DANIX; `null` khi không có.",
            "nullable": true
          },
          "originalState": {
            "type": "string",
            "enum": [
              "pending",
              "stored",
              "gone"
            ],
            "description": "`stored`: bản gốc có ở `url`. `pending`: chưa có bản gốc trên kho của DANIX. Tệp của hội thoại lâu không hoạt động đã được cất đi; lượt đọc trang ĐẦU tin nhắn (`GET /conversations/{id}/messages` không `cursor`) hay lượt nhân viên mở hội thoại kéo nó về ở nền trong ít phút — đọc lại sau để nhận `url`. Tệp chưa từng lưu bản gốc thì được lưu khi nhân viên mở xem. `gone`: nguồn không còn, sẽ không bao giờ có."
          },
          "fileName": {
            "type": "string",
            "description": "Tên tệp.",
            "nullable": true
          },
          "mimeType": {
            "type": "string",
            "description": "Loại nội dung (MIME).",
            "nullable": true
          }
        },
        "required": [
          "id",
          "type",
          "url",
          "previewUrl",
          "originalState",
          "fileName",
          "mimeType"
        ],
        "description": "Một tệp đính kèm của tin nhắn.",
        "title": "MessageAttachment"
      },
      "Message": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã tin nhắn."
          },
          "conversationId": {
            "type": "string",
            "format": "uuid",
            "description": "Hội thoại chứa tin."
          },
          "pageId": {
            "type": "string",
            "format": "uuid",
            "description": "Mã trang trong DANIX (`id` của `GET /pages`)."
          },
          "providerPageId": {
            "type": "string",
            "description": "Mã trang ở kênh gốc."
          },
          "provider": {
            "type": "string",
            "enum": [
              "facebook_page",
              "zalo_oa",
              "zalo_personal"
            ],
            "description": "Kênh của tin nhắn."
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound"
            ],
            "description": "`inbound` khách gửi, `outbound` shop gửi."
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "image",
              "video",
              "audio",
              "file",
              "sticker",
              "location",
              "reel",
              "share",
              "like",
              "postback",
              "order",
              "referral"
            ],
            "description": "Loại tin nhắn."
          },
          "text": {
            "type": "string",
            "description": "Nội dung chữ.",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "sent",
              "delivered",
              "read",
              "failed"
            ],
            "description": "Trạng thái gửi."
          },
          "isDeleted": {
            "type": "boolean",
            "description": "Tin đã bị thu hồi."
          },
          "sentByName": {
            "type": "string",
            "description": "Tên nhân viên hay ứng dụng đã gửi; rỗng với tin của khách.",
            "nullable": true
          },
          "sentByIsIntegration": {
            "type": "boolean",
            "description": "`true` khi tin do một ứng dụng kết nối gửi."
          },
          "customerName": {
            "type": "string",
            "description": "Tên khách của hội thoại; với nhóm (`GROUP`) là người gửi tin.",
            "nullable": true
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MessageAttachment"
            },
            "description": "Tệp đính kèm."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Thời điểm tin được ghi nhận. ISO 8601, múi giờ UTC."
          }
        },
        "required": [
          "id",
          "conversationId",
          "pageId",
          "providerPageId",
          "provider",
          "direction",
          "type",
          "text",
          "status",
          "isDeleted",
          "sentByName",
          "sentByIsIntegration",
          "customerName",
          "attachments",
          "createdAt"
        ],
        "description": "Một tin nhắn hoặc bình luận.",
        "title": "Message"
      },
      "ChatTag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Mã thẻ."
          },
          "name": {
            "type": "string",
            "description": "Tên thẻ."
          },
          "color": {
            "type": "string",
            "enum": [
              "red",
              "orange",
              "amber",
              "green",
              "teal",
              "cyan",
              "blue",
              "indigo",
              "purple",
              "pink"
            ],
            "description": "Màu của thẻ."
          },
          "pageId": {
            "type": "string",
            "format": "uuid",
            "description": "Trang sở hữu thẻ. Thẻ là của TỪNG trang: chỉ gắn được vào hội thoại cùng trang, và hai trang có thể có thẻ trùng tên."
          }
        },
        "required": [
          "id",
          "name",
          "color",
          "pageId"
        ],
        "description": "Một thẻ hội thoại.",
        "title": "ChatTag"
      }
    },
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "dnx_live_…",
        "description": "Khoá API theo shop do quản trị viên tạo trong ứng dụng Tự động hoá. Gửi `Authorization: Bearer dnx_live_…`."
      }
    }
  },
  "security": [
    {
      "ApiKey": []
    }
  ]
}
