English
English
Appearance
English
English
Appearance
Context
Outbound campaigns need to rotate across multiple caller IDs to avoid being marked as spam by carriers. The Zorio admin manages outbound numbers inside rotation groups through the admin UI; an integrator only needs to read the list of groups in order to assign them to a campaign (through the caller_id_group_id field when creating a Campaign).
Read-only endpoints
Managing individual caller IDs, creating / updating / deleting groups (POST/PUT/DELETE), syncing members and clearing cooldowns all happen through the Zorio admin UI and are not exposed via the Integration API.
| Endpoint | Purpose |
|---|---|
GET /api/caller-id-groups | List the customer's rotation groups |
GET /api/caller-id-groups/{id} | Detail for one group + list of its caller-ID members |
GET /api/caller-id-groups/{id}/monitor | Per-member runtime snapshot (eligibility, cooldown, health) — for monitoring dashboards |
GET /api/caller-id-groups — list This endpoint only returns the groups owned by the customer (automatically scoped by authentication). No additional filters.
data[]) | Field | Type | Description | Valid values |
|---|---|---|---|
id | integer | Rotation group ID | |
name | string | Group name | max 150 chars |
description | string|null | Internal description | max 500 chars |
rotation_strategy | string | Caller-ID selection strategy when dialing out | round_robin / least_used / cap_based / weighted / sticky_lead / random |
sticky_enabled | bool | Whether the caller ID used for a lead is reused on subsequent calls | true / false |
status | string | Group status — disabled groups are skipped by the engine | active / disabled |
caller_ids_count | integer | Number of active caller-ID members | ≥ 0 |
created_at | datetime | When the group was created (ISO 8601 UTC) |
Response 200:
{
"data": [
{
"id": 3,
"name": "Group A - Viettel Sales",
"description": "Viettel outbound numbers for the summer campaign",
"rotation_strategy": "round_robin",
"sticky_enabled": true,
"status": "active",
"caller_ids_count": 12,
"created_at": "2026-05-20T03:00:00Z"
}
]
}GET /api/caller-id-groups/{id} — detail | Field | Type | Description |
|---|---|---|
id | integer | Group ID (from the list above) |
Includes every field from the list plus a caller_ids[] array:
| Field | Type | Description | Valid values |
|---|---|---|---|
caller_ids[] | array | List of caller-ID members | |
caller_ids[].id | integer | Caller-ID record ID | |
caller_ids[].number | string | Outbound phone number | E.164 or domestic |
caller_ids[].display_name | string|null | Internal display name | max 100 chars |
caller_ids[].carrier | string | Telco | viettel / mobifone / vinaphone / vietnamobile / landline / other |
caller_ids[].carrier_label | string | Carrier display label | |
caller_ids[].status | string | Caller-ID status | active / disabled |
caller_ids[].trunk_name | string|null | SIP trunk bound to this caller ID | |
caller_ids[].weight | integer | Weight when rotation_strategy = weighted | ≥ 1 (default 1) |
Response 200:
{
"data": {
"id": 3,
"name": "Group A - Viettel Sales",
"description": "...",
"rotation_strategy": "round_robin",
"sticky_enabled": true,
"status": "active",
"caller_ids_count": 12,
"created_at": "2026-05-20T03:00:00Z",
"caller_ids": [
{
"id": 18,
"number": "0900000010",
"display_name": "Sales Line 1",
"carrier": "viettel",
"carrier_label": "Viettel",
"status": "active",
"trunk_name": "Trunk-Viettel-01",
"weight": 2
}
]
}
}GET /api/caller-id-groups/{id}/monitor — runtime snapshot For realtime monitoring dashboards — shows which caller IDs are currently eligible / in cooldown / over quota.
| Field | Type | Description |
|---|---|---|
id | integer | Group ID |
| Field | Type | Description | Valid values |
|---|---|---|---|
group_id | integer | Group ID | |
group_name | string | Group name | |
total_members | integer | Total caller-ID members | |
eligible_now | integer | Caller IDs ready to be picked | |
in_cooldown | integer | Caller IDs in temporary cooldown | |
disabled | integer | Caller IDs disabled manually | |
members[] | array | Per-caller-ID snapshot | |
members[].caller_id_id | integer | Caller-ID ID | |
members[].number | string | Phone number | |
members[].carrier | string | Telco | viettel / mobifone / vinaphone / vietnamobile / landline / other |
members[].status | string | Runtime status | eligible / cooldown / disabled / quota_exceeded |
members[].health_score | integer | Health score — lower means more signs of carrier flagging | 0-100 |
members[].calls_today | integer | Calls dialed today | ≥ 0 |
members[].daily_cap | integer | Daily call cap | ≥ 1 |
members[].cooldown_until | datetime|null | When cooldown ends (null if not in cooldown) | ISO 8601 UTC |
Response 200:
{
"data": {
"group_id": 3,
"group_name": "Group A - Viettel Sales",
"total_members": 12,
"eligible_now": 9,
"in_cooldown": 2,
"disabled": 1,
"members": [
{
"caller_id_id": 18,
"number": "0900000010",
"carrier": "viettel",
"status": "eligible",
"health_score": 82,
"calls_today": 87,
"daily_cap": 200,
"cooldown_until": null
},
{
"caller_id_id": 19,
"number": "0900000011",
"carrier": "mobifone",
"status": "cooldown",
"health_score": 14,
"calls_today": 53,
"daily_cap": 200,
"cooldown_until": "2026-06-06T12:00:00Z"
}
]
}
}Response 404: { "message": "Pool not found." }.
In the body when creating a Campaign, set caller_id_group_id to the id returned by GET /api/caller-id-groups:
{
"name": "Summer Promo",
"caller_id_group_id": 3,
...
}When the campaign transitions draft → active (PUT /api/telesales/campaigns/{id}/status), the caller_id_group_id field is required — missing it returns 422.