# API cấp phát

> Tạo, tạm khoá và tính tiền tài khoản hosting từ hệ thống của bạn qua API cấp phát /api/v1: token, endpoint, idempotency, giới hạn tần suất và ví dụ.

Source: https://zopanel.net/vi/docs/provisioning-api  
Updated: 2026-10-07

API cấp phát tại `/api/v1` cho phép hệ thống billing hoặc CMS riêng của bạn quản lý tài khoản hosting trên server ZoPanel. Các module WHMCS, Blesta, HostBill và Paymenter đều dùng API này. Tài khoản được xác định bằng username, gói bằng tên gói, nên hệ thống của bạn không cần lưu ID nào của ZoPanel. Mô tả đầy đủ có ở dạng file OpenAPI 3 (`docs/provisioning-openapi.yaml` trong mã nguồn).

## Xác thực

1. Trong ZoPanel, mở **Tài khoản của tôi → API token** và bấm **Tạo token**.
2. Chọn **Quyền** là **Chỉ cấp phát hosting (WHMCS, CMS)**.
3. Trong **Chỉ cho phép từ IP**, nhập địa chỉ của các server gọi API (tối đa 20 IP hoặc dải mạng).
4. Sao chép token `zpat_…`. Token chỉ hiển thị một lần.

Gửi token kèm mọi request:

```http
Authorization: Bearer zpat_xxxxxxxxxxxx
```

API token cần license Pro. Lưu ý:

- Token **Chỉ cấp phát hosting** chỉ gọi được `/api/v1`. Mọi đường dẫn khác trả về `403`.
- Gọi từ địa chỉ không có trong danh sách của token sẽ bị trả về `403`.
- Nếu bạn bật **Cài đặt → Chung → Giới hạn truy cập panel**, server gọi API cũng phải có trong danh sách đó.
- Nếu panel bắt buộc 2FA với chủ token, chủ token phải bật 2FA, nếu không token bị từ chối.
- Đổi mật khẩu của chủ token sẽ thu hồi mọi token của người đó.

### Token của ai

| Chủ token | Token làm được gì |
| --- | --- |
| Quản trị viên | Bán các gói của quản trị viên và thấy mọi tài khoản hosting |
| Đại lý | Chỉ bán gói của đại lý và chỉ thấy khách của đại lý, trong giới hạn gói của đại lý |

## Endpoint

URL gốc: `https://panel.example.com:8888/api/v1`

| Phương thức và đường dẫn | Mục đích |
| --- | --- |
| `GET /api/v1` | Phiên bản API, chủ token và địa chỉ panel (kiểm tra kết nối) |
| `GET /api/v1/packages` | Các gói bạn được bán |
| `PUT /api/v1/packages/{name}` | Tạo hoặc cập nhật gói theo tên |
| `POST /api/v1/accounts` | Tạo tài khoản, có thể kèm website đầu tiên |
| `GET /api/v1/accounts/{username}` | Trạng thái, gói và mức sử dụng |
| `POST /api/v1/accounts/{username}/suspend` | Tạm khoá (gọi lặp lại không sao) |
| `POST /api/v1/accounts/{username}/unsuspend` | Mở khoá (gọi lặp lại không sao) |
| `PUT /api/v1/accounts/{username}/package` | Đổi gói |
| `PUT /api/v1/accounts/{username}/password` | Đặt mật khẩu panel và SFTP |
| `POST /api/v1/accounts/{username}/login` | Link đăng nhập dùng một lần, hiệu lực 60 giây |
| `DELETE /api/v1/accounts/{username}` | Xoá tài khoản |
| `GET /api/v1/usage` | Mức sử dụng của mọi tài khoản bạn quản lý |

Username dài 3–16 ký tự, gồm chữ thường và số, bắt đầu bằng chữ. Tên bắt đầu bằng `zp` được dành riêng. Mật khẩu dài 8–128 ký tự.

### Ghi chú cho từng lời gọi

- **Tạo tài khoản:** bắt buộc có `username`, `password` và `package` (tên hoặc ID). `email`, `domain` và `php` là tuỳ chọn. Kết quả gồm `account`, kèm `site` hoặc `site_error`. Nếu tài khoản đã tạo mà website thì không (ví dụ tên miền đang host ở nơi khác), đơn hàng vẫn thành công và khách có thể thêm website sau.
- **Đăng nhập:** trả về `url`, `path` và `expires_in`. Chỉ dùng được cho tài khoản khách hàng đang hoạt động. Đại lý đăng nhập bằng mật khẩu và lớp xác thực thứ hai của chính họ.
- **Xoá tài khoản:** website, database, mail và file của tài khoản bị gỡ. Dữ liệu vẫn được giữ trên server theo thời gian đặt trong **Cài đặt → Chung → Giữ dữ liệu tài khoản đã xoá (ngày)**.
- **Gói:** trường không gửi lên sẽ nhận giá trị 0. Với hầu hết giới hạn, 0 là không giới hạn. Ngoại lệ: với `io_mbps` và `db_connections`, 0 là tự động và -1 là không giới hạn. `php_memory_mb` mặc định 256, `php_children` mặc định 10.

### Trường của tài khoản và mức sử dụng

`GET /accounts/{username}` và từng phần tử của `GET /usage` trả về:

| Trường | Ý nghĩa |
| --- | --- |
| `username`, `email`, `package` | Thông tin tài khoản |
| `status` | `active` hoặc `suspended` |
| `created_at` | Unix time |
| `usage.disk_mb`, `usage.disk_limit_mb` | Dung lượng đã dùng và giới hạn của gói (0 = không giới hạn), cập nhật mỗi giờ |
| `usage.bandwidth_mb` | Lưu lượng đã phục vụ trong tháng hiện tại, tính từ access log, cập nhật mỗi giờ |
| `usage.sites`, `usage.sites_limit` | Website |
| `usage.databases`, `usage.databases_limit` | Database |

## Mã lỗi

Lỗi trả về body JSON `{"error": "…"}` kèm mã trạng thái tương ứng:

| Mã | Ý nghĩa |
| --- | --- |
| 400 | Dữ liệu không hợp lệ, ví dụ gói không tồn tại |
| 401 | Thiếu token, token sai hoặc đã hết hạn |
| 402 | Vượt giới hạn license, hoặc chưa có license Pro |
| 403 | Token không được phép (phạm vi quyền, địa chỉ IP hoặc chính sách 2FA) |
| 404 | Tài khoản không tồn tại hoặc không thuộc quyền quản lý của bạn |
| 409 | Một request cùng idempotency key vẫn đang chạy |
| 429 | Quá nhiều request |

## Idempotency key

Gửi header `Idempotency-Key` duy nhất với các request `POST`, `PUT` và `DELETE`, ví dụ `billing-create-<mã đơn hàng>`. Key dài tối đa 200 ký tự.

- Nếu cùng một token gọi lại cùng phương thức, cùng URL với cùng key trong vòng 24 giờ, nó nhận lại kết quả lần đầu kèm header `Idempotent-Replayed: true`. Thao tác không bị thực hiện lần nữa, nên gọi lại sau timeout không bao giờ tạo trùng tài khoản.
- Trong lúc request đầu còn chạy, request lặp lại nhận `409`.
- Lỗi server (`5xx`) và kết quả `429` không được lưu, nên có thể gọi lại với cùng key.

## Giới hạn tần suất

Mỗi token được gọi tối đa 1200 request mỗi phút. Vượt mức này API trả về `429` kèm `Retry-After: 60`. Xác thực sai liên tục từ cùng một địa chỉ cũng bị giới hạn.

## Ví dụ

```bash
T="Authorization: Bearer zpat_xxx"
J="Content-Type: application/json"
P=https://panel.example.com:8888/api/v1

# Kiểm tra kết nối
curl -H "$T" $P

# Đẩy một gói lên
curl -X PUT -H "$T" -H "$J" \
  -d '{"max_sites":3,"disk_mb":10240,"memory_mb":1024,"cpu_percent":100,"sftp":true}' \
  $P/packages/Starter

# Bán gói (website có SSL khi tên miền đã trỏ về server)
curl -X POST -H "$T" -H "$J" -H "Idempotency-Key: order-1001" \
  -d '{"username":"shop1","password":"S3cure-Pass-1","email":"owner@shop1.vn","package":"Starter","domain":"shop1.vn","php":"8.3"}' \
  $P/accounts

# Hoá đơn quá hạn, rồi đã thanh toán
curl -X POST -H "$T" $P/accounts/shop1/suspend
curl -X POST -H "$T" $P/accounts/shop1/unsuspend

# Nâng cấp gói
curl -X PUT -H "$T" -H "$J" -d '{"package":"Business"}' $P/accounts/shop1/package

# Nút "Đăng nhập control panel": chuyển khách tới .url
curl -X POST -H "$T" $P/accounts/shop1/login

# Lấy mức sử dụng để tính tiền
curl -H "$T" $P/usage
```

## Các trường của gói

| Trường | Ý nghĩa |
| --- | --- |
| `max_sites`, `max_databases`, `max_cron`, `max_ftp`, `max_mailboxes` | Số lượng (0 = không giới hạn) |
| `disk_mb`, `memory_mb` | Dung lượng ổ đĩa và RAM (0 = không giới hạn) |
| `cpu_percent` | CPU, 100 = một nhân (0 = không giới hạn) |
| `processes` | Số tiến trình (0 = không giới hạn) |
| `io_mbps` | Tốc độ đĩa (0 = tự động, -1 = không giới hạn) |
| `db_connections` | Số kết nối database mỗi user (0 = tự động, -1 = không giới hạn) |
| `php_memory_mb`, `php_children` | Giới hạn bộ nhớ PHP và số PHP worker |
| `sftp`, `terminal` | Quyền truy cập, `true` hoặc `false` |
