> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yotel.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Number groups

> Read your number groups, and use them to choose which numbers a campaign dials from.

A **number group** is a named set of your phone numbers, for example
"Chennai mobiles". Send a campaign's `did_group_ids` and the campaign dials
**only** from the numbers in those groups.

You create and edit groups in the app, under **Settings → Number groups**.
The API only **reads** groups: use it to find group ids and to see which
numbers can dial right now.

## How a group campaign picks a number

* It uses **only** the numbers in its groups. It **never** uses your other
  numbers — not even when no number in the groups can dial.
* Several groups work as one list. A number in two groups counts once.
* A number that is **resting**, **warming** or **retired** stays in its group
  but is not used. When it is active again, the campaign uses it again by
  itself. You do not need to do anything.
* A change to a group (a number added or removed) applies from the next call.
  No restart is needed.
* Your number rules (a new number for each attempt, the same number after a
  contact) still work, but only inside the group's numbers.

> [!NOTE]
>
> A campaign uses **either** one fixed number (`caller_id`) **or** number
> groups (`did_group_ids`), never both. Sending both returns `422`. Without
> either, the campaign uses all of your numbers.

## When no number in the groups can dial

The campaign **waits**. It does not borrow another of your numbers.

* **At start:** `POST /api/v1/campaigns/{id}/start` returns `409` with the
  reason, for example *"Every number in this campaign's groups is RESTING…"*.
* **While running:** the campaign stays `running` but places no calls. Leads
  stay `pending`. The campaign's `caller_id_block` field shows the reason
  (it is `null` again once the campaign can dial).
* **Email:** after about 2 minutes of waiting, the person who created the
  campaign, your tenant admins and the people listed in **Settings → Alerts**
  get **one** email. When the campaign can dial again, they get **one**
  "dialling again" email. (If you pause or stop the campaign instead, no
  "dialling again" email is sent.) A campaign created with an API key has no
  creator, so the creator email is skipped.
* **Running low:** a running group campaign with only one number left that
  can dial, or with all its numbers close to their daily limit, sends one
  warning email per day.
* Turn these emails off for one campaign with
  `alert_caller_id_blocked: false`, or for all campaigns in
  **Settings → Alerts → No usable caller ID**.

### Reasons

| `reason` | What it means | What to do |
| - | - | - |
| `group_no_dids` | The groups have no outbound number. | Add outbound numbers to the group, or pick other groups. |
| `group_dids_warming` | Every number in the groups is still warming. | Wait. They are used as soon as they become active. |
| `group_dids_resting` | Every number in the groups is resting (flagged as spam). | Wait for the rest to end, or add another number to the group. |
| `group_dids_unusable` | Every number in the groups is retired or turned off. | Add an active outbound number to the group. |
| `group_no_dial_capacity` | Every number in the groups reached its daily call limit or is busy. | Dialling continues when a number is free. Add numbers to dial more. |

***

## List number groups

Returns all of your number groups, sorted by name.

```bash curl theme={null}
curl https://api.yotel.in/api/v1/did-groups \
  -H "Authorization: Bearer yt_live_YOUR_KEY"
```

```python Python theme={null}
groups = client.did_groups.list()
for g in groups:
    print(g.id, g.name, g.counts.dialable, "of", g.counts.total, "can dial")
```

```typescript TypeScript theme={null}
const groups = await client.didGroups.list();
for (const g of groups) {
  console.log(g.id, g.name, `${g.counts.dialable} of ${g.counts.total} can dial`);
}
```

**Scope:** `campaigns:read`  |  **Status:** `200 OK`

### Response

```json theme={null}
{
  "groups": [
    {
      "id": "6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
      "name": "Chennai mobiles",
      "color": "#2563eb",
      "description": "Numbers for Tamil Nadu leads",
      "created_at": "2026-10-14T05:00:00Z",
      "updated_at": "2026-10-14T05:00:00Z",
      "counts": {
        "total": 4,
        "dialable": 2,
        "active": 3,
        "warming": 0,
        "resting": 1,
        "retired": 0,
        "inactive": 0,
        "inbound_only": 0
      },
      "campaigns": [
        { "id": "a1b2c3d4-0000-4000-8000-000000000001", "name": "October outreach", "status": "running" }
      ]
    }
  ]
}
```

| Field | Type | Description |
| - | - | - |
| `id` | string | Group id. Use it in a campaign's `did_group_ids`. |
| `name` | string | Group name |
| `color` | string \| null | Colour shown in the app, e.g. `#2563eb` |
| `description` | string \| null | Free text |
| `counts.total` | integer | Numbers in the group |
| `counts.dialable` | integer | Numbers that can dial **right now** |
| `counts.active` / `warming` / `resting` / `retired` | integer | Numbers in each status |
| `counts.inactive` | integer | Numbers that are turned off |
| `counts.inbound_only` | integer | Numbers that can only receive calls |
| `campaigns` | array | Campaigns that use the group: `id`, `name`, `status` |

***

## Get a number group

Returns one group with its numbers and the live state of each number.

```bash curl theme={null}
curl https://api.yotel.in/api/v1/did-groups/{group_id} \
  -H "Authorization: Bearer yt_live_YOUR_KEY"
```

```python Python theme={null}
group = client.did_groups.get("group_id")
for m in group.members:
    print(m.number, "can dial" if m.dialable else m.why_not)
```

```typescript TypeScript theme={null}
const group = await client.didGroups.get("group_id");
for (const m of group.members) {
  console.log(m.number, m.dialable ? "can dial" : m.why_not);
}
```

**Scope:** `campaigns:read`  |  **Status:** `200 OK`

Returns `404` if the group does not exist or belongs to another tenant.

### Response

The same fields as in the list, plus `members`:

```json theme={null}
{
  "id": "6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "name": "Chennai mobiles",
  "counts": { "total": 2, "dialable": 1, "active": 1, "warming": 0, "resting": 1, "retired": 0, "inactive": 0, "inbound_only": 0 },
  "members": [
    {
      "did_id": "0b7e6c1a-0000-4000-8000-000000000001",
      "number": "07969671463",
      "label": "Chennai 1",
      "carrier": "trunk",
      "role": "outbound",
      "status": "active",
      "is_active": true,
      "health_score": 92.0,
      "calls_today": 40,
      "daily_cap": 150,
      "resting_until": null,
      "retired_at": null,
      "dialable": true,
      "why_not": null,
      "added_at": "2026-10-14T05:00:00Z"
    },
    {
      "did_id": "0b7e6c1a-0000-4000-8000-000000000002",
      "number": "07969671464",
      "label": "Chennai 2",
      "carrier": "trunk",
      "role": "outbound",
      "status": "resting",
      "is_active": true,
      "health_score": 41.0,
      "calls_today": 0,
      "daily_cap": 150,
      "resting_until": "2026-10-18T00:00:00Z",
      "retired_at": null,
      "dialable": false,
      "why_not": "resting",
      "added_at": "2026-10-14T05:00:00Z"
    }
  ],
  "campaigns": [],
  "color": "#2563eb",
  "description": null,
  "created_at": "2026-10-14T05:00:00Z",
  "updated_at": "2026-10-14T05:00:00Z"
}
```

| Field | Type | Description |
| - | - | - |
| `did_id` | string | The number's id |
| `number` | string | The phone number |
| `label` | string \| null | Name of the number |
| `role` | string | `outbound`, `both` or `inbound` |
| `status` | string | `active`, `warming`, `resting` or `retired` |
| `is_active` | boolean | `false` when the number is turned off |
| `health_score` | number \| null | Reputation score, 0–100 |
| `calls_today` / `daily_cap` | integer | Calls made today, and the most allowed per day |
| `resting_until` | string \| null | When a resting number comes back |
| `dialable` | boolean | `true` if the number can dial **right now** |
| `why_not` | string \| null | Why it cannot dial (see below). `null` = it can. |

### `why_not` values

| Value | Meaning |
| - | - |
| `warming` | The number is new and still warming up. It is used once it is active. |
| `resting` | The number was flagged as spam and is resting. It comes back after `resting_until`. |
| `retired` | The number is retired and will not dial again. |
| `inactive` | The number is turned off. |
| `inbound_only` | The number can only receive calls. |
| `wrong_partition` | The number is in the test partition and your account is not (or the other way round). |
| `low_health` | The number's `health_score` is too low (under 60). |
| `daily_cap` | The number made its `daily_cap` calls today. It dials again tomorrow. |

New values may be added later. Treat an unknown value as "cannot dial".

***

## Use groups on a campaign

Send the group ids as `did_group_ids` when you create or update a campaign
(up to 20 groups).

```bash curl theme={null}
curl -X POST https://api.yotel.in/api/v1/campaigns \
  -H "Authorization: Bearer yt_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "October outreach",
    "dial_mode": "progressive",
    "did_group_ids": ["6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f"]
  }'
```

```python Python theme={null}
campaign = client.campaigns.create(
    name="October outreach",
    dial_mode="progressive",
    did_group_ids=["6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f"],
)
```

```typescript TypeScript theme={null}
const campaign = await client.campaigns.create({
  name: "October outreach",
  dial_mode: "progressive",
  did_group_ids: ["6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f"],
});
```

* To go back to all of your numbers, update with `"did_group_ids": []`.
* To move a campaign from one fixed number to groups, send
  `"caller_id": null` and the `did_group_ids` in the **same** update.
* Pause a running campaign before you update it (see
  [Campaigns CRUD](https://docs.yotel.in/api-reference/campaigns/crud.md#editing-a-running-campaign)).
  You do **not** need to pause it to change the numbers **inside** a group.

See [Campaigns CRUD](https://docs.yotel.in/api-reference/campaigns/crud.md) for all campaign fields.

***

## Errors

| Status | When |
| - | - |
| `404` | `GET /api/v1/did-groups/{group_id}`: the group does not exist or belongs to another tenant |
| `409` | Campaign start: no number in the campaign's groups can dial (the message gives the reason) |
| `422` | Campaign create / update: `caller_id` and `did_group_ids` sent together, a group id that is not a UUID, a group that is not yours, or more than 20 groups |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.