> ## 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.

# Campaigns API

> Create campaigns, upload contacts, and read stats over HTTP.

The `/campaign` surface manages outbound calling campaigns: the campaign itself, the contacts you dial, the schedule and agent **configurations** that drive them, and the org-wide **concurrency** limit. Every route lives under the `/campaign` prefix on `https://api.anyreach.ai`.

For the product walkthrough, see [Campaigns](/campaigns/overview). For credentials and the `X-Anyreach-Org` header, see [Authentication](/api-reference/authentication).

## Endpoints

### Campaigns

| Method  | Path                             | Scope              | Description                         |
| ------- | -------------------------------- | ------------------ | ----------------------------------- |
| `POST`  | `/campaign/campaigns`            | `campaigns:manage` | Create a campaign. Starts `active`. |
| `GET`   | `/campaign/campaigns`            | `campaigns:read`   | List campaigns. Cursor pagination.  |
| `GET`   | `/campaign/campaigns/{id}`       | `campaigns:read`   | Get one campaign.                   |
| `PATCH` | `/campaign/campaigns/{id}`       | `campaigns:manage` | Update a campaign.                  |
| `POST`  | `/campaign/campaigns/{id}/start` | `campaigns:manage` | Start or resume the campaign.       |
| `POST`  | `/campaign/campaigns/{id}/pause` | `campaigns:manage` | Pause a running campaign.           |
| `POST`  | `/campaign/campaigns/{id}/close` | `campaigns:manage` | Close the campaign permanently.     |

### Contacts

| Method   | Path                                             | Scope              | Description                                                 |
| -------- | ------------------------------------------------ | ------------------ | ----------------------------------------------------------- |
| `POST`   | `/campaign/campaigns/{id}/contacts`              | `campaigns:manage` | Upsert up to 1000 contacts.                                 |
| `GET`    | `/campaign/campaigns/{id}/contacts`              | `campaigns:read`   | List contacts with their attempts. Limit/offset pagination. |
| `GET`    | `/campaign/campaigns/{id}/contacts/{contact_id}` | `campaigns:read`   | Get one contact.                                            |
| `DELETE` | `/campaign/campaigns/{id}/contacts/{contact_id}` | `campaigns:manage` | Delete a contact.                                           |

### Stats

| Method | Path                             | Scope            | Description                                            |
| ------ | -------------------------------- | ---------------- | ------------------------------------------------------ |
| `GET`  | `/campaign/campaigns/{id}/stats` | `campaigns:read` | Contact counts, attempt counts, and active-call count. |

### Configurations

| Method   | Path                                   | Scope              | Description                                                |
| -------- | -------------------------------------- | ------------------ | ---------------------------------------------------------- |
| `POST`   | `/campaign/configurations`             | `campaigns:manage` | Create a configuration.                                    |
| `GET`    | `/campaign/configurations`             | `campaigns:read`   | List configurations.                                       |
| `GET`    | `/campaign/configurations/{config_id}` | `campaigns:read`   | Get one configuration.                                     |
| `PATCH`  | `/campaign/configurations/{config_id}` | `campaigns:manage` | Update a configuration.                                    |
| `DELETE` | `/campaign/configurations/{config_id}` | `campaigns:manage` | Delete a configuration. Fails if a campaign references it. |
| `GET`    | `/campaign/timezones`                  | —                  | List valid IANA timezone identifiers.                      |

### Concurrency settings

| Method | Path                             | Scope                | Description                                             |
| ------ | -------------------------------- | -------------------- | ------------------------------------------------------- |
| `GET`  | `/campaign/concurrency-settings` | `concurrency:read`   | Get the org concurrency limit and current active count. |
| `PUT`  | `/campaign/concurrency-settings` | `concurrency:manage` | Set the org concurrency limit.                          |

## Scopes

Read endpoints accept either `campaigns:read` or `campaigns:manage`. Mutating endpoints — create, update, start, pause, close, upsert, delete — require `campaigns:manage`.

The concurrency endpoints have their own scopes: `GET /campaign/concurrency-settings` accepts `campaigns:read`, `campaigns:manage`, `concurrency:read`, or `concurrency:manage`; `PUT` accepts `campaigns:manage` or `concurrency:manage`.

A `403` means the credential is valid but lacks the required scope. See [Authentication](/api-reference/authentication) for how scopes are granted.

## Conventions

### Resolving by external\_id

Wherever a path takes a campaign `{id}` or a `{contact_id}`, you can pass either the resource's UUID or the `external_id` you assigned on create. This applies to campaign, contact, start/pause/close, and stats routes — set `external_id` once and address the resource by your own key thereafter.

<Note>
  Pass `default` as the campaign `{id}` on the contacts routes to manage ad-hoc contacts that belong to no campaign. Contacts added under `default` must already carry valid E.164 phone numbers, since there is no configuration to supply a default country code.
</Note>

### Pagination

The two list endpoints paginate differently.

| Endpoint                                | Style  | Parameters        |
| --------------------------------------- | ------ | ----------------- |
| `GET /campaign/campaigns`               | Cursor | `cursor`, `limit` |
| `GET /campaign/campaigns/{id}/contacts` | Offset | `limit`, `offset` |

`GET /campaign/campaigns` also accepts `name` (substring filter) and `status` (repeatable) query parameters. `GET .../contacts` accepts a `status` filter. The contacts response echoes `total`, `limit`, and `offset` alongside the `contacts` array.

### Limits

| Limit                                                    | Value                   |
| -------------------------------------------------------- | ----------------------- |
| `limit` on either list endpoint                          | Max `200`, default `50` |
| Contacts per `POST .../contacts` request                 | Max `1000`              |
| Contacts inlined on `POST` / `PATCH /campaign/campaigns` | Max `5000`              |
| Default concurrency limit (when unset)                   | `10`                    |

### Stats response

`GET /campaign/campaigns/{id}/stats` returns contact counts by status, attempt totals, and live activity:

```json theme={null}
{
  "campaign_id": "…",
  "status": "active",
  "contacts": {
    "total": 0,
    "pending": 0,
    "in_progress": 0,
    "completed": 0,
    "exhausted": 0
  },
  "attempts": {
    "total": 0,
    "average_per_contact": 0.0
  },
  "active_calls": 0,
  "concurrency_limit": 10
}
```

The stats route resolves the campaign by UUID only.

### Upsert response

`POST /campaign/campaigns/{id}/contacts` upserts: contacts are updated when their `id` or `external_id` matches an existing row, and inserted otherwise. It returns counts plus the resolved IDs:

```json theme={null}
{
  "inserted": 0,
  "updated": 0,
  "failed": [],
  "warnings": [],
  "contact_ids": []
}
```

Phone numbers are normalized to E.164 using the campaign configuration's `default_country_code` (falling back to `+1`). Numbers that can't be parsed return `422`. You cannot add contacts to a campaign whose `status` is `closed` (`400`).

## Example: bulk-add contacts

Upsert two contacts into a campaign addressed by its `external_id`. With an org API key (`ak_`) the organization is implicit; with a personal access token (`pat_`) add the `X-Anyreach-Org` header.

```bash theme={null}
curl -X POST https://api.anyreach.ai/campaign/campaigns/spring-outreach/contacts \
  -H "Authorization: Bearer $ANYREACH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      {
        "external_id": "lead-1001",
        "phone_numbers": ["+14155550123"],
        "data": { "first_name": "Ada" }
      },
      {
        "external_id": "lead-1002",
        "phone_numbers": ["+14155550199"],
        "data": { "first_name": "Grace" }
      }
    ]
  }'
```

## Related

<CardGroup cols={2}>
  <Card title="Campaigns" icon="megaphone" href="/campaigns/overview">
    The product walkthrough for building and running outbound campaigns.
  </Card>

  <Card title="Authentication" icon="key" href="/api-reference/authentication">
    Bearer credentials, the X-Anyreach-Org header, and scopes.
  </Card>
</CardGroup>
