{
  "openapi": "3.1.0",
  "info": {
    "title": "WA Auto API",
    "version": "1.1.0",
    "description": "Khoá API gửi qua header Authorization: Bearer <khoá>. Tối đa 60 yêu cầu/phút mỗi khoá; mọi phản hồi có RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset. Mọi lệnh POST cần header Idempotency-Key. Thân yêu cầu tối đa 64 KiB."
  },
  "servers": [
    {
      "url": "https://wa.builera.app/api/public/v1"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer"
      }
    }
  },
  "paths": {
    "/contacts": {
      "get": {
        "operationId": "listContacts",
        "summary": "Danh sách khách",
        "description": "Mới nhất trước. Lọc theo từ khoá, nhãn hoặc số điện thoại.",
        "tags": [
          "contacts"
        ],
        "x-required-scope": "contacts.read",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Tìm theo tên, số, email, công ty.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "Tên nhãn.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "phone",
            "in": "query",
            "required": false,
            "description": "Số điện thoại (0901…, +84901…).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Số dòng mỗi trang, 1–100 (mặc định 50).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Giá trị nextCursor của trang trước.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
                      "phone": "+84901234567",
                      "name": "Chị Lan",
                      "whatsappName": "Lan Spa Hoa",
                      "email": "lan@example.com",
                      "company": "Spa Hoa",
                      "notes": null,
                      "stageId": null,
                      "dealValue": 2500000,
                      "doNotContact": false,
                      "tags": [
                        "VIP"
                      ],
                      "createdAt": "2026-10-17T03:15:00.000Z",
                      "lastInboundAt": "2026-10-17T07:28:00.000Z",
                      "lastOutboundAt": "2026-10-17T07:29:00.000Z"
                    }
                  ],
                  "nextCursor": "MjAyNi0xMC0xN1QwMzoxNTowMC4wMDBafDdiMDI0ZTA2…"
                }
              }
            }
          },
          "400": {
            "description": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_required",
                    "message": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_key",
                    "message": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Gói chưa có API hoặc đã hết hạn.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "plan_no_api",
                    "message": "Gói chưa có API hoặc đã hết hạn."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Không có đối tượng này trong workspace của khoá.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Không có đối tượng này trong workspace của khoá."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "request_in_progress",
                    "message": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Nội dung quá 64 KiB (tính theo byte).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "body_too_large",
                    "message": "Nội dung quá 64 KiB (tính theo byte)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "message": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "server_error",
                    "message": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ)."
                  }
                }
              }
            }
          },
          "503": {
            "description": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limit_unavailable",
                    "message": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After."
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createContact",
        "summary": "Tạo khách",
        "description": "Số được chuẩn hoá theo quốc gia mặc định của workspace. Trùng số trả 409 kèm contactId có sẵn.",
        "tags": [
          "contacts"
        ],
        "x-required-scope": "contacts.write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Mỗi lệnh một giá trị mới (1–255 ký tự). Gửi lại cùng khoá + cùng nội dung trong 24 giờ nhận đúng kết quả cũ kèm Idempotent-Replayed: true.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "phone": "0901234567",
                "name": "Chị Lan",
                "email": "lan@example.com",
                "company": "Spa Hoa",
                "tags": [
                  "Website"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
                    "phone": "+84901234567",
                    "name": "Chị Lan",
                    "whatsappName": "Lan Spa Hoa",
                    "email": "lan@example.com",
                    "company": "Spa Hoa",
                    "notes": null,
                    "stageId": null,
                    "dealValue": 2500000,
                    "doNotContact": false,
                    "tags": [
                      "VIP"
                    ],
                    "createdAt": "2026-10-17T03:15:00.000Z",
                    "lastInboundAt": "2026-10-17T07:28:00.000Z",
                    "lastOutboundAt": "2026-10-17T07:29:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_required",
                    "message": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_key",
                    "message": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Gói chưa có API hoặc đã hết hạn.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "plan_no_api",
                    "message": "Gói chưa có API hoặc đã hết hạn."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Không có đối tượng này trong workspace của khoá.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Không có đối tượng này trong workspace của khoá."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "request_in_progress",
                    "message": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Nội dung quá 64 KiB (tính theo byte).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "body_too_large",
                    "message": "Nội dung quá 64 KiB (tính theo byte)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "message": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "server_error",
                    "message": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ)."
                  }
                }
              }
            }
          },
          "503": {
            "description": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limit_unavailable",
                    "message": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/contacts/{contactId}": {
      "get": {
        "operationId": "getContact",
        "summary": "Xem một khách",
        "tags": [
          "contacts"
        ],
        "x-required-scope": "contacts.read",
        "parameters": [
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "description": "Mã khách.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
                    "phone": "+84901234567",
                    "name": "Chị Lan",
                    "whatsappName": "Lan Spa Hoa",
                    "email": "lan@example.com",
                    "company": "Spa Hoa",
                    "notes": null,
                    "stageId": null,
                    "dealValue": 2500000,
                    "doNotContact": false,
                    "tags": [
                      "VIP"
                    ],
                    "createdAt": "2026-10-17T03:15:00.000Z",
                    "lastInboundAt": "2026-10-17T07:28:00.000Z",
                    "lastOutboundAt": "2026-10-17T07:29:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_required",
                    "message": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_key",
                    "message": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Gói chưa có API hoặc đã hết hạn.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "plan_no_api",
                    "message": "Gói chưa có API hoặc đã hết hạn."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Không có đối tượng này trong workspace của khoá.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Không có đối tượng này trong workspace của khoá."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "request_in_progress",
                    "message": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Nội dung quá 64 KiB (tính theo byte).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "body_too_large",
                    "message": "Nội dung quá 64 KiB (tính theo byte)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "message": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "server_error",
                    "message": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ)."
                  }
                }
              }
            }
          },
          "503": {
            "description": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limit_unavailable",
                    "message": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After."
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateContact",
        "summary": "Sửa khách",
        "description": "Chỉ gửi trường cần đổi. Không đổi được số điện thoại qua API.",
        "tags": [
          "contacts"
        ],
        "x-required-scope": "contacts.write",
        "parameters": [
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "description": "Mã khách.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "name": "Chị Lan (Spa Hoa Q3)",
                "dealValue": 3000000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
                    "phone": "+84901234567",
                    "name": "Chị Lan",
                    "whatsappName": "Lan Spa Hoa",
                    "email": "lan@example.com",
                    "company": "Spa Hoa",
                    "notes": null,
                    "stageId": null,
                    "dealValue": 2500000,
                    "doNotContact": false,
                    "tags": [
                      "VIP"
                    ],
                    "createdAt": "2026-10-17T03:15:00.000Z",
                    "lastInboundAt": "2026-10-17T07:28:00.000Z",
                    "lastOutboundAt": "2026-10-17T07:29:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_required",
                    "message": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_key",
                    "message": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Gói chưa có API hoặc đã hết hạn.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "plan_no_api",
                    "message": "Gói chưa có API hoặc đã hết hạn."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Không có đối tượng này trong workspace của khoá.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Không có đối tượng này trong workspace của khoá."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "request_in_progress",
                    "message": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Nội dung quá 64 KiB (tính theo byte).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "body_too_large",
                    "message": "Nội dung quá 64 KiB (tính theo byte)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "message": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "server_error",
                    "message": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ)."
                  }
                }
              }
            }
          },
          "503": {
            "description": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limit_unavailable",
                    "message": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/contacts/{contactId}/tags": {
      "post": {
        "operationId": "tagContact",
        "summary": "Gắn / bỏ nhãn",
        "description": "Nhãn chưa có sẽ được tạo. Gắn nhãn kích hoạt luật tự động “Khách được gắn nhãn”.",
        "tags": [
          "contacts"
        ],
        "x-required-scope": "contacts.write",
        "parameters": [
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "description": "Mã khách.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Mỗi lệnh một giá trị mới (1–255 ký tự). Gửi lại cùng khoá + cùng nội dung trong 24 giờ nhận đúng kết quả cũ kèm Idempotent-Replayed: true.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "add": [
                  "VIP"
                ],
                "remove": [
                  "Mới"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "id": "7b024e06-36ae-46d0-91d1-2185cd71837f",
                    "tags": [
                      "VIP"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_required",
                    "message": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_key",
                    "message": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Gói chưa có API hoặc đã hết hạn.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "plan_no_api",
                    "message": "Gói chưa có API hoặc đã hết hạn."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Không có đối tượng này trong workspace của khoá.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Không có đối tượng này trong workspace của khoá."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "request_in_progress",
                    "message": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Nội dung quá 64 KiB (tính theo byte).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "body_too_large",
                    "message": "Nội dung quá 64 KiB (tính theo byte)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "message": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "server_error",
                    "message": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ)."
                  }
                }
              }
            }
          },
          "503": {
            "description": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limit_unavailable",
                    "message": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/conversations": {
      "get": {
        "operationId": "listConversations",
        "summary": "Danh sách hội thoại",
        "description": "Mới có tin trước.",
        "tags": [
          "conversations"
        ],
        "x-required-scope": "conversations.read",
        "parameters": [
          {
            "name": "accountId",
            "in": "query",
            "required": false,
            "description": "Chỉ hội thoại của một số WhatsApp.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "open | pending | closed",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "unanswered",
            "in": "query",
            "required": false,
            "description": "true: chỉ hội thoại khách đang chờ trả lời.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Số dòng mỗi trang, 1–100 (mặc định 50).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Giá trị nextCursor của trang trước.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "id": "1cc08299-a808-4936-a102-c4045341130f",
                      "accountId": "a3e6f771-6ab9-4614-9165-7be3d240ffcf",
                      "type": "direct",
                      "contactId": "7b024e06-36ae-46d0-91d1-2185cd71837f",
                      "title": "Chị Lan",
                      "status": "open",
                      "assigneeMemberId": null,
                      "unread": 2,
                      "waitingSince": "2026-10-17T07:28:00.000Z",
                      "archived": false,
                      "lastMessageAt": "2026-10-17T07:28:00.000Z",
                      "lastMessagePreview": "Cho mình hỏi giá combo"
                    }
                  ],
                  "nextCursor": null
                }
              }
            }
          },
          "400": {
            "description": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_required",
                    "message": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_key",
                    "message": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Gói chưa có API hoặc đã hết hạn.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "plan_no_api",
                    "message": "Gói chưa có API hoặc đã hết hạn."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Không có đối tượng này trong workspace của khoá.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Không có đối tượng này trong workspace của khoá."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "request_in_progress",
                    "message": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Nội dung quá 64 KiB (tính theo byte).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "body_too_large",
                    "message": "Nội dung quá 64 KiB (tính theo byte)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "message": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "server_error",
                    "message": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ)."
                  }
                }
              }
            }
          },
          "503": {
            "description": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limit_unavailable",
                    "message": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/conversations/{conversationId}": {
      "get": {
        "operationId": "getConversation",
        "summary": "Xem một hội thoại",
        "tags": [
          "conversations"
        ],
        "x-required-scope": "conversations.read",
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "description": "Mã hội thoại.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "id": "1cc08299-a808-4936-a102-c4045341130f",
                    "type": "direct",
                    "status": "open"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_required",
                    "message": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_key",
                    "message": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Gói chưa có API hoặc đã hết hạn.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "plan_no_api",
                    "message": "Gói chưa có API hoặc đã hết hạn."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Không có đối tượng này trong workspace của khoá.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Không có đối tượng này trong workspace của khoá."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "request_in_progress",
                    "message": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Nội dung quá 64 KiB (tính theo byte).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "body_too_large",
                    "message": "Nội dung quá 64 KiB (tính theo byte)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "message": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "server_error",
                    "message": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ)."
                  }
                }
              }
            }
          },
          "503": {
            "description": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limit_unavailable",
                    "message": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/conversations/{conversationId}/messages": {
      "get": {
        "operationId": "listMessages",
        "summary": "Tin nhắn trong hội thoại",
        "description": "Mới nhất trước. Tệp đính kèm chỉ có tên và loại tệp.",
        "tags": [
          "conversations"
        ],
        "x-required-scope": "conversations.read",
        "parameters": [
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "description": "Mã hội thoại.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Số dòng mỗi trang, 1–100 (mặc định 50).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Giá trị nextCursor của trang trước.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "id": "4f0c…",
                      "direction": "in",
                      "kind": "text",
                      "text": "Cho mình hỏi giá combo",
                      "status": "sent",
                      "at": "2026-10-17T07:28:00.000Z",
                      "sentBy": "contact",
                      "contactId": "7b024e06-36ae-46d0-91d1-2185cd71837f",
                      "campaignId": null,
                      "attachments": []
                    }
                  ],
                  "nextCursor": null
                }
              }
            }
          },
          "400": {
            "description": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_required",
                    "message": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_key",
                    "message": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Gói chưa có API hoặc đã hết hạn.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "plan_no_api",
                    "message": "Gói chưa có API hoặc đã hết hạn."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Không có đối tượng này trong workspace của khoá.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Không có đối tượng này trong workspace của khoá."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "request_in_progress",
                    "message": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Nội dung quá 64 KiB (tính theo byte).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "body_too_large",
                    "message": "Nội dung quá 64 KiB (tính theo byte)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "message": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "server_error",
                    "message": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ)."
                  }
                }
              }
            }
          },
          "503": {
            "description": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limit_unavailable",
                    "message": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/messages": {
      "post": {
        "operationId": "sendMessage",
        "summary": "Gửi tin",
        "description": "Tin được xếp vào cổng giao việc như mọi tin khác: luôn chịu danh sách loại trừ (STOP), và chịu hạn mức, làm nóng, giờ gửi nếu chủ số đã bật. Gửi cho người đã nhắn tới số này tính là tin tự động; gửi cho người mới đi làn chiến dịch (chậm hơn, có thể bị hoãn). Trả 202 khi đã xếp hàng — theo dõi kết quả bằng webhook message.status. Gửi lại cùng Idempotency-Key không gửi trùng (trường idempotencyKey trong thân vẫn nhận như tên cũ của tiêu đề, sẽ bỏ).",
        "tags": [
          "messages"
        ],
        "x-required-scope": "messages.send",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Mỗi lệnh một giá trị mới (1–255 ký tự). Gửi lại cùng khoá + cùng nội dung trong 24 giờ nhận đúng kết quả cũ kèm Idempotent-Replayed: true.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "accountId": "a3e6f771-6ab9-4614-9165-7be3d240ffcf",
                "phone": "0901234567",
                "text": "Chào chị Lan, lịch hẹn của chị là 10h sáng thứ 7 ạ."
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "messageId": "9d2e…",
                    "jobId": "c51a…",
                    "conversationId": "1cc08299-a808-4936-a102-c4045341130f",
                    "status": "sending",
                    "duplicate": false,
                    "lane": "reply"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_required",
                    "message": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_key",
                    "message": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Gói chưa có API hoặc đã hết hạn.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "plan_no_api",
                    "message": "Gói chưa có API hoặc đã hết hạn."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Không có đối tượng này trong workspace của khoá.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Không có đối tượng này trong workspace của khoá."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "request_in_progress",
                    "message": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Nội dung quá 64 KiB (tính theo byte).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "body_too_large",
                    "message": "Nội dung quá 64 KiB (tính theo byte)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "message": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "server_error",
                    "message": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ)."
                  }
                }
              }
            }
          },
          "503": {
            "description": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limit_unavailable",
                    "message": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/campaigns": {
      "get": {
        "operationId": "listCampaigns",
        "summary": "Danh sách chiến dịch",
        "tags": [
          "campaigns"
        ],
        "x-required-scope": "campaigns",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Số dòng mỗi trang, 1–100 (mặc định 50).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "id": "e1b0…",
                      "name": "Chăm sóc khách tháng 10",
                      "kind": "sequence",
                      "status": "running",
                      "targets": 120,
                      "startedAt": "2026-10-15T02:00:00.000Z",
                      "finishedAt": null,
                      "createdAt": "2026-10-14T09:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_required",
                    "message": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_key",
                    "message": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Gói chưa có API hoặc đã hết hạn.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "plan_no_api",
                    "message": "Gói chưa có API hoặc đã hết hạn."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Không có đối tượng này trong workspace của khoá.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Không có đối tượng này trong workspace của khoá."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "request_in_progress",
                    "message": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Nội dung quá 64 KiB (tính theo byte).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "body_too_large",
                    "message": "Nội dung quá 64 KiB (tính theo byte)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "message": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "server_error",
                    "message": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ)."
                  }
                }
              }
            }
          },
          "503": {
            "description": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limit_unavailable",
                    "message": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/campaigns/{campaignId}/enroll": {
      "post": {
        "operationId": "enrollCampaign",
        "summary": "Thêm khách vào chiến dịch",
        "description": "Bản nháp: khách được thêm vào danh sách, lọc an toàn khi bắt đầu. Đã hẹn giờ: chỉ thêm người qua bộ lọc an toàn ngay bây giờ (`excluded` đếm theo lý do), lọc lại lúc bắt đầu. Đang chạy hoặc tạm dừng: lọc an toàn ngay, người không đạt được ghi lý do.",
        "tags": [
          "campaigns"
        ],
        "x-required-scope": "campaigns",
        "parameters": [
          {
            "name": "campaignId",
            "in": "path",
            "required": true,
            "description": "Mã chiến dịch.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Mỗi lệnh một giá trị mới (1–255 ký tự). Gửi lại cùng khoá + cùng nội dung trong 24 giờ nhận đúng kết quả cũ kèm Idempotent-Replayed: true.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "contactIds": [
                  "7b024e06-36ae-46d0-91d1-2185cd71837f"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "added": 1,
                    "enrolled": 1,
                    "alreadyIn": 0,
                    "excluded": {},
                    "unknown": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_required",
                    "message": "Lệnh POST thiếu tiêu đề Idempotency-Key, hoặc khoá không phải 1–255 ký tự in được."
                  }
                }
              }
            }
          },
          "401": {
            "description": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_key",
                    "message": "Thiếu khoá, sai khoá hoặc khoá đã thu hồi."
                  }
                }
              }
            }
          },
          "403": {
            "description": "Gói chưa có API hoặc đã hết hạn.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "plan_no_api",
                    "message": "Gói chưa có API hoặc đã hết hạn."
                  }
                }
              }
            }
          },
          "404": {
            "description": "Không có đối tượng này trong workspace của khoá.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "Không có đối tượng này trong workspace của khoá."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "request_in_progress",
                    "message": "Yêu cầu đầu với cùng Idempotency-Key còn đang chạy; thử lại theo Retry-After."
                  }
                }
              }
            }
          },
          "413": {
            "description": "Nội dung quá 64 KiB (tính theo byte).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "body_too_large",
                    "message": "Nội dung quá 64 KiB (tính theo byte)."
                  }
                }
              }
            }
          },
          "422": {
            "description": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "idempotency_key_reused",
                    "message": "Idempotency-Key đã dùng cho một yêu cầu có nội dung khác."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Quá 60 yêu cầu/phút cho khoá; chờ theo header Retry-After."
                  }
                }
              }
            }
          },
          "500": {
            "description": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ).",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "server_error",
                    "message": "Lỗi máy chủ; thử lại sau (cùng Idempotency-Key — lỗi máy chủ không được nhớ)."
                  }
                }
              }
            }
          },
          "503": {
            "description": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "rate_limit_unavailable",
                    "message": "Tạm thời không kiểm được giới hạn hoặc khoá chống trùng; lệnh không chạy, thử lại theo Retry-After."
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}