Máy nhánh (Extensions)
Quản lý máy nhánh SIP của tổng đài: liệt kê, tạo mới, sửa, xoá, lấy số khả dụng, xoay mật khẩu.
GET /api/pbx/extensions — Liệt kê
Query parameters
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
search | string | optional | Tìm theo extension_number hoặc display_name (LIKE) | |
status | string | optional | Lọc theo trạng thái config | active, disabled |
page | integer | optional | Số trang | ≥ 1 (default 1) |
per_page | integer | optional | Số bản ghi/trang | 1-200 (default 50) |
Response fields (mỗi item trong data[])
| Field | Type | Mô tả | Giá trị |
|---|---|---|---|
extension_number | string | Số máy nhánh — SIP register name | 3-10 chữ số |
display_name | string|null | Tên hiển thị (caller ID name khi gọi nội bộ) | max 100 ký tự |
voicemail_enabled | bool | Có bật voicemail không | true / false |
outbound_caller_id | string|null | Số gọi ra mặc định | Phải tồn tại trong /caller-ids |
recording_mode | string | Chế độ ghi âm | none, inbound, outbound, all |
status | string | Trạng thái cấu hình admin | active, disabled |
team_id | integer|null | ID nhóm | |
department_id | integer|null | ID phòng ban | |
max_concurrent | integer | Số cuộc gọi đồng thời tối đa | 1-10 |
tls_enabled | bool | Có bật SIPS/TLS không | true / false |
created_at | datetime | Ngày tạo (ISO 8601 + offset) | |
updated_at | datetime | Ngày cập nhật cuối |
Response Body (JSON) — 200
json
{
"current_page": 1,
"data": [
{
"extension_number": "1001",
"display_name": "Mac I5",
"voicemail_enabled": true,
"outbound_caller_id": null,
"recording_mode": "all",
"status": "active",
"team_id": 2,
"department_id": null,
"max_concurrent": 2,
"tls_enabled": false,
"created_at": "2026-04-06T12:19:13+00:00",
"updated_at": "2026-06-18T11:01:47+00:00"
}
],
"last_page": 2,
"per_page": 10,
"total": 17
}GET /api/pbx/extensions/{ext} — Chi tiết
Path parameter
| Field | Type | Mô tả |
|---|---|---|
ext | string | extension_number (KHÔNG phải id) |
Response = 12 field như list + 1 field extra sip_registration realtime.
Response Body (JSON) — 200
json
{
"data": {
"extension_number": "1001",
"display_name": "Mac I5",
"voicemail_enabled": true,
"outbound_caller_id": null,
"recording_mode": "all",
"status": "active",
"team_id": 2,
"department_id": null,
"max_concurrent": 2,
"tls_enabled": false,
"created_at": "2026-04-06T12:19:13+00:00",
"updated_at": "2026-06-18T11:01:47+00:00",
"sip_registration": "registered"
}
}| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
status | string | Cấu hình admin | active, disabled |
sip_registration | string | Trạng thái SIP register realtime — CRM dùng để biết softphone online hay không | registered, unregistered, unknown |
POST /api/pbx/extensions — Tạo mới
Request body fields
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
extension_number | string | ✅ | Số máy nhánh — unique per tài khoản | Regex ^[0-9]{3,10}$ (3-10 chữ số) |
display_name | string | optional | Tên hiển thị | max 100 ký tự |
team_id | integer | optional | ID nhóm | Phải tồn tại trong teams |
sip_password | string | optional | Mật khẩu SIP register — KHÔNG truyền → tự sinh 24 ký tự alphanumeric | min 8, max 100 ký tự |
voicemail_enabled | bool | optional | Bật voicemail | true / false (default false) |
voicemail_pin | string | optional | PIN truy cập voicemail | max 20 ký tự |
recording_mode | string | optional | Chế độ ghi âm | none / inbound / outbound / all (default none) |
max_concurrent | integer | optional | Cuộc gọi đồng thời tối đa | 1-10 (default 2) |
outbound_caller_id | string | optional | Số gọi ra mặc định | Phải tồn tại trong /api/pbx/caller-ids (active) |
Request Body (JSON)
json
{
"extension_number": "9001",
"display_name": "Sales 01",
"voicemail_enabled": true,
"recording_mode": "all",
"max_concurrent": 2,
"outbound_caller_id": "0900000020"
}Response body fields (12 field như list + 2 field bổ sung khi tạo)
| Field | Type | Mô tả |
|---|---|---|
sip_password | string | Mật khẩu SIP — chỉ trả 1 lần, copy vào softphone ngay |
generated_password | bool | true nếu Zorio tự sinh, false nếu client truyền |
Response Body (JSON) — 201
json
{
"data": {
"extension_number": "9001",
"display_name": "Sales 01",
"voicemail_enabled": true,
"recording_mode": "all",
"status": "active",
"max_concurrent": 2,
"outbound_caller_id": "0900000020",
"team_id": null,
"department_id": null,
"tls_enabled": false,
"created_at": "2026-06-29T04:53:59+00:00",
"updated_at": "2026-06-29T04:53:59+00:00",
"sip_password": "<sip_password_chỉ_hiển_thị_1_lần>",
"generated_password": true
}
}PUT /api/pbx/extensions/{ext} — Cập nhật
Request body fields (mọi field optional — chỉ truyền field cần đổi)
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
display_name | string|null | Tên hiển thị | max 100 ký tự |
team_id | integer|null | ID nhóm | |
voicemail_enabled | bool | Bật voicemail | true / false |
voicemail_pin | string|null | PIN voicemail | max 20 |
recording_mode | string | Chế độ ghi âm | none / inbound / outbound / all |
max_concurrent | integer | Cuộc đồng thời tối đa | 1-10 |
outbound_caller_id | string|null | Số gọi ra | Phải có trong /api/pbx/caller-ids (active) |
status | string | Trạng thái cấu hình | active / disabled |
Lưu ý
extension_number và sip_password KHÔNG đổi qua PUT. Đổi sip_password → dùng endpoint /change-password.
Request Body (JSON)
json
{
"display_name": "Sales 01 (renamed)",
"voicemail_enabled": false,
"max_concurrent": 4,
"recording_mode": "all",
"outbound_caller_id": "0900000020"
}Response Body (JSON) — 200
json
{
"data": {
"extension_number": "1001",
"display_name": "Sales 01 (renamed)",
"voicemail_enabled": false,
"outbound_caller_id": "0900000020",
"recording_mode": "all",
"status": "active",
"team_id": null,
"department_id": null,
"max_concurrent": 4,
"tls_enabled": false,
"created_at": "2026-04-06T12:19:13+00:00",
"updated_at": "2026-06-29T07:55:12+00:00"
}
}DELETE /api/pbx/extensions/{ext} — Xoá
Không có Request Body.
Response Body (JSON) — 200
json
{
"data": { "deleted": true }
}Soft delete + flush FS directory cache.
GET /api/pbx/caller-ids — Liệt kê số khả dụng
Trả các outbound caller ID status=active của khách hàng — dùng khi pick outbound_caller_id cho ext hoặc caller_id cho click-to-call.
Query parameters
| Field | Type | Mô tả | Giá trị |
|---|---|---|---|
search | string | Match number hoặc display_name | |
page | integer | Số trang | ≥ 1 |
per_page | integer | Số bản ghi/trang | 1-200 (default 50) |
Response body fields
| Field | Type | Mô tả | Giá trị |
|---|---|---|---|
number | string | Số gọi ra (E.164 hoặc nội địa VN) | 8-15 chữ số |
display_name | string|null | Tên hiển thị nội bộ | |
carrier | string | Nhà mạng | viettel / mobifone / vinaphone / vietnamobile / landline / other |
cli_type | string | Loại số | mobile / fixed_02x / hotline_1900 / hotline_1800 / brandname |
status | string | Trạng thái | active (luôn vì query lọc) |
Response Body (JSON) — 200
json
{
"current_page": 1,
"data": [
{ "number": "0900000020", "display_name": "POC", "carrier": "landline", "cli_type": "mobile", "status": "active" },
{ "number": "0900000021", "display_name": "POC", "carrier": "landline", "cli_type": "mobile", "status": "active" }
],
"last_page": 1, "per_page": 50, "total": 7
}Khi POST/PUT extension với outbound_caller_id không có trong list → HTTP 422:
json
{ "errors": { "outbound_caller_id": ["Giá trị đã chọn cho outbound caller id không hợp lệ."] } }POST /api/pbx/extensions/{ext}/change-password — Xoay mật khẩu SIP
Request body fields
| Field | Type | Required | Mô tả | Giá trị |
|---|---|---|---|---|
new_password | string | optional | Mật khẩu SIP mới — không truyền → tự sinh 24 ký tự alphanumeric | min 8, max 100 |
Request Body (JSON) — tự sinh password (body rỗng)
json
{}Request Body (JSON) — đặt password thủ công
json
{
"new_password": "MyNewStrongPwd123"
}Response body fields
| Field | Type | Mô tả |
|---|---|---|
extension_number | string | Số máy nhánh đã đổi |
sip_password | string | Mật khẩu mới — chỉ trả 1 lần |
generated_password | bool | true nếu Zorio tự sinh, false nếu client truyền |
Response Body (JSON) — 200
json
{
"data": {
"extension_number": "1001",
"sip_password": "AbC123...",
"generated_password": true
}
}