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

# Dialing, concurrency, and retries

> How calls are paced, retried, and resolved.

A background dialer worker continuously polls for contacts that are due to be called, allocates a bounded number of concurrent call slots, and starts one call attempt per eligible contact. Each attempt picks a phone number, places the call, waits for it to finish, and then either marks the contact done or schedules a retry. This page explains how pacing, retries, and outcomes are decided.

For where contacts and configurations come from, see [Campaigns overview](/campaigns/overview). For how finished attempts surface, see [Results and analytics](/campaigns/results-and-analytics).

## Concurrency

Two limits bound how many calls run at once.

| Limit                    | Scope                    | Default | Maximum |
| ------------------------ | ------------------------ | ------- | ------- |
| `concurrency_limit`      | Per organization         | `10`    | `10000` |
| Global concurrency limit | Across all organizations | `50`    | —       |

The per-org limit is read or changed through the `/campaign/concurrency-settings` endpoint. If an organization has never set one, it falls back to `10`.

```bash theme={null}
curl https://api.anyreach.ai/campaign/concurrency-settings \
  -H "Authorization: Bearer <token>" \
  -H "X-Anyreach-Org: <organization_id>"
```

Reading requires one of `campaigns:read`, `campaigns:manage`, `concurrency:read`, or `concurrency:manage`. Updating requires `campaigns:manage` or `concurrency:manage`.

**Active calls** for an organization counts contacts in either the `locked` or `in_progress` status. An organization is at capacity when its active count reaches its `concurrency_limit`.

### How slots are allocated

On each cycle the dialer computes how many global slots are free (`global limit − total active calls across all orgs`). If none are free, it waits for the next cycle. Free slots are then distributed proportionally to each organization's `concurrency_limit`, but never more than the room that organization has left (`concurrency_limit − active calls`). Whole slots are handed out first by proportional floor, then any leftover slots go to the organizations with the largest fractional shares.

<Note>
  The per-org limit caps your simultaneous calls, but the shared global limit means the exact number running at any instant also depends on demand from other organizations.
</Note>

## The dialer loop

The dialer runs as a continuous worker, polling roughly once per second by default. Each cycle does the following.

<Steps>
  <Step title="Sweep stale locks">
    Contacts stuck in `locked` longer than the threshold are returned to `pending` so they can be retried.
  </Step>

  <Step title="Find eligible timezones">
    For each active campaign configuration, the dialer evaluates the calling windows and works out which timezones are currently inside their window. Contacts not attached to a campaign bypass timezone filtering and are always eligible.
  </Step>

  <Step title="Allocate slots">
    It reads per-org concurrency stats, computes free global slots, and allocates them proportionally (see above).
  </Step>

  <Step title="Fetch contacts">
    It pulls due contacts that fit the allocation and the eligible timezones, ordered by priority. A contact is due when it is `pending` and any pending retry time has passed.
  </Step>

  <Step title="Start attempts">
    It starts one call attempt per fetched contact, in parallel.
  </Step>
</Steps>

Contacts are ordered by campaign priority (highest first); contacts with no campaign sort above all campaign-priority contacts. Multiple dialer instances can run safely — row locking prevents the same contact from being dialed twice.

## A single attempt

Each attempt selects which phone number to call based on how many attempts the contact has already had, places the call, and waits for it to complete.

### Phone number selection

A contact can carry several phone numbers in order. Each number is tried up to `max_attempts_per_phone_number` times (default `2`) before the dialer moves on to the next number in the list. When every number has used all of its attempts, the contact is exhausted.

```
attempts 0,1  -> phone_numbers[0]   (number 1, attempts 1 and 2)
attempts 2,3  -> phone_numbers[1]   (number 2, attempts 1 and 2)
attempts 4,5  -> phone_numbers[2]   (number 3, attempts 1 and 2)
...           -> exhausted once the list runs out
```

### Per-call timeout

After the call is initiated, the attempt waits up to **30 minutes** for a completion signal. If no signal arrives in that window, the attempt is recorded as a `timeout`.

### Attempt outcomes

| Outcome     | Meaning                                                          |
| ----------- | ---------------------------------------------------------------- |
| `completed` | The call finished and reported success.                          |
| `failed`    | The call ended without reporting success (and did not time out). |
| `timeout`   | No completion signal arrived within the 30-minute window.        |

## Retries and resolution

What happens after an attempt depends on whether it completed and whether any attempts remain.

| Result of attempt                 | Next state of contact                                             |
| --------------------------------- | ----------------------------------------------------------------- |
| `completed`                       | Contact marked completed — no further calls.                      |
| Not completed, attempts remain    | Retry scheduled about an hour later (`RETRY_DELAY_MINUTES = 60`). |
| Not completed, no attempts remain | Contact marked `exhausted`.                                       |

A scheduled retry sets a `retry_after` time roughly one hour ahead; the dialer will not pick the contact up again until that time has passed. When retried, the attempt count advances, which is what eventually moves selection to the next phone number and then to exhaustion.

<Info>
  "Attempts remain" means the next attempt index still maps to a phone number in the contact's list, given `max_attempts_per_phone_number`. A contact with no phone numbers at all is exhausted immediately.
</Info>

## Where to go next

<CardGroup cols={2}>
  <Card title="Campaigns overview" icon="bullhorn" href="/campaigns/overview">
    How campaigns, contacts, and configurations fit together.
  </Card>

  <Card title="Results and analytics" icon="chart-line" href="/campaigns/results-and-analytics">
    Inspect attempts, dispositions, and call outcomes.
  </Card>
</CardGroup>
