Skip to content

Caller-ID rotation groups

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 summary

EndpointPurpose
GET /api/caller-id-groupsList 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}/monitorPer-member runtime snapshot (eligibility, cooldown, health) — for monitoring dashboards

GET /api/caller-id-groups — list

Query parameters

This endpoint only returns the groups owned by the customer (automatically scoped by authentication). No additional filters.

Response fields (each item in data[])

FieldTypeDescriptionValid values
idintegerRotation group ID
namestringGroup namemax 150 chars
descriptionstring|nullInternal descriptionmax 500 chars
rotation_strategystringCaller-ID selection strategy when dialing outround_robin / least_used / cap_based / weighted / sticky_lead / random
sticky_enabledboolWhether the caller ID used for a lead is reused on subsequent callstrue / false
statusstringGroup status — disabled groups are skipped by the engineactive / disabled
caller_ids_countintegerNumber of active caller-ID members≥ 0
created_atdatetimeWhen the group was created (ISO 8601 UTC)

Response 200:

json
{
  "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

Path parameter

FieldTypeDescription
idintegerGroup ID (from the list above)

Response fields

Includes every field from the list plus a caller_ids[] array:

FieldTypeDescriptionValid values
caller_ids[]arrayList of caller-ID members
caller_ids[].idintegerCaller-ID record ID
caller_ids[].numberstringOutbound phone numberE.164 or domestic
caller_ids[].display_namestring|nullInternal display namemax 100 chars
caller_ids[].carrierstringTelcoviettel / mobifone / vinaphone / vietnamobile / landline / other
caller_ids[].carrier_labelstringCarrier display label
caller_ids[].statusstringCaller-ID statusactive / disabled
caller_ids[].trunk_namestring|nullSIP trunk bound to this caller ID
caller_ids[].weightintegerWeight when rotation_strategy = weighted≥ 1 (default 1)

Response 200:

json
{
  "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.

Path parameter

FieldTypeDescription
idintegerGroup ID

Response fields

FieldTypeDescriptionValid values
group_idintegerGroup ID
group_namestringGroup name
total_membersintegerTotal caller-ID members
eligible_nowintegerCaller IDs ready to be picked
in_cooldownintegerCaller IDs in temporary cooldown
disabledintegerCaller IDs disabled manually
members[]arrayPer-caller-ID snapshot
members[].caller_id_idintegerCaller-ID ID
members[].numberstringPhone number
members[].carrierstringTelcoviettel / mobifone / vinaphone / vietnamobile / landline / other
members[].statusstringRuntime statuseligible / cooldown / disabled / quota_exceeded
members[].health_scoreintegerHealth score — lower means more signs of carrier flagging0-100
members[].calls_todayintegerCalls dialed today≥ 0
members[].daily_capintegerDaily call cap≥ 1
members[].cooldown_untildatetime|nullWhen cooldown ends (null if not in cooldown)ISO 8601 UTC

Response 200:

json
{
  "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." }.

Assigning a group to a campaign

In the body when creating a Campaign, set caller_id_group_id to the id returned by GET /api/caller-id-groups:

json
{
  "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.

Cấp phép theo điều khoản sử dụng của Zorio.