# Campaigns

A [journey](https://voice.sphoro.com/docs/journeys) run over a list of contacts, paced against your own concurrency, pausable while it runs, and reportable while it is still going. This is the answer to "I have four thousand numbers".

## The shape of it

```
create ──▶ upload entries ──▶ start ──┬──▶ pause ──▶ resume
                                       │
                                       ├──▶ cancel
                                       │
                                       └──▶ report   (readable throughout)
```

## 1 · Create it

`POST /v1/journey_campaigns`

**Shell**

```bash
curl -s -X POST https://voice.sphoro.com/v1/journey_campaigns \
  -H "Authorization: Bearer $SPHORO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "journey_id": "'"$JOURNEY_ID"'",
    "name": "September collections"
  }'
```

**Response201 Created**

```json
{
  "id": "cmp_5d81a3f70b2e94c6",
  "journey_id": "jny_2c4e…",
  "name": "September collections",
  "status": "draft",
  "entry_count": 0
}
```

A campaign is pinned to the journey's **published** version when it starts. Publishing a new version mid-campaign does not change what the running one does — which is what you want, and is why publishing during a campaign is safe.

## 2 · Load the contacts

`POST /v1/journey_campaigns/{id}/entries`

**Shell**

```bash
curl -s -X POST https://voice.sphoro.com/v1/journey_campaigns/$CAMPAIGN_ID/entries \
  -H "Authorization: Bearer $SPHORO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "entries": [
      {"contact": {"phone": "+919876543210", "email": "priya@example.com"},
       "variables": {"first_name": "Priya", "amount_due": 8400}},
      {"contact": {"phone": "+919812345678"},
       "variables": {"first_name": "Arun",  "amount_due": 2200}}
    ]
  }'
```

Every variable the journey's agents need must be here. A contact missing one is refused at upload rather than failing when it is dialled three days later — see [variables](https://voice.sphoro.com/docs/variables).

> **Upload in batches and check the refusals.** A four-thousand row load that comes back with sixty refusals is sixty people who will never be called, and the only moment you will be told is now.

## 3 · Start, and control it while it runs

`POST /v1/journey_campaigns/{id}/start`

`POST /v1/journey_campaigns/{id}/pause`

`POST /v1/journey_campaigns/{id}/resume`

`POST /v1/journey_campaigns/{id}/cancel`

**Shell**

```bash
curl -s -X POST https://voice.sphoro.com/v1/journey_campaigns/$CAMPAIGN_ID/start \
  -H "Authorization: Bearer $SPHORO_API_KEY"
```

| Action | Does |
| --- | --- |
| **Pause** | Stops starting new contacts, and holds every contact already walking the graph on the node they are standing on. Calls already connected finish normally. |
| **Resume** | Starts new contacts again, and releases the held ones within about thirty seconds, from where each left off. |
| **Cancel** | Stops for good. Executions in flight are ended. |

> **Pause is the button to know about before you start.** The moment to discover the greeting says the wrong month is forty calls in, and pause is what turns that into forty rather than four thousand.

## 4 · Read the report while it runs

`GET /v1/journey_campaigns/{id}/report`

**Shell**

```bash
curl -s https://voice.sphoro.com/v1/journey_campaigns/$CAMPAIGN_ID/report \
  -H "Authorization: Bearer $SPHORO_API_KEY"
```

**Response200 OK**

```json
{
  "campaign_id": "cmp_5d81a3f70b2e94c6",
  "status": "running",
  "entries": { "total": 4000, "started": 1240, "running": 86, "finished": 1154 },
  "outcomes": { "converted": 402, "unreachable": 611, "opted_out": 12, "stuck": 3, "expired": 0 },
  "nodes": {
    "remind":  { "entered": 1240, "left": 1154 },
    "hold":    { "entered": 700,  "left": 700 },
    "sms":     { "entered": 611,  "left": 611 },
    "arrange": { "entered": 402,  "left": 402 }
  }
}
```

The `nodes` block is the funnel: how many contacts entered each node and how many left. It is where a badly-drawn graph shows up — a node with far fewer leaving than entering has contacts standing on it, and `stuck` in the outcomes says some of them ran out of cases.

## Pacing

A campaign paces its own dialling against your account's concurrency ceiling. It does not fire every contact at once, which is the mistake a hand-rolled loop makes — see [concurrency](https://voice.sphoro.com/docs/capacity) for what that costs.

Two things still gate every individual call, and they are the reason a campaign's numbers never quite match its entry count:

- **Compliance.** Suppressed numbers, missing consent and out-of-hours calls are refused before dialling. See [compliance](https://voice.sphoro.com/docs/compliance).
- **Calling windows.** A `wait` node with a window holds the contact until the window opens where *they* are.

## Listing them

`GET /v1/journey_campaigns`

`GET /v1/journey_campaigns/{id}`

**Shell**

```bash
curl -s https://voice.sphoro.com/v1/journey_campaigns \
  -H "Authorization: Bearer $SPHORO_API_KEY"
```

## Before your first real campaign

1. **Run the journey on yourself.** One execution, `POST /v1/journey_executions`, your own number. You will find something.
2. **Run it on ten colleagues.** A campaign of ten, started and watched to completion.
3. **Check the suppression list is loaded**, and that consent is where it needs to be. See [compliance](https://voice.sphoro.com/docs/compliance).
4. **Then load four thousand** — and keep the pause endpoint to hand.
