# Campaigns API

> Create an Edesy Collect reminder campaign, save and render its message, upload debtors and start it - request fields, defaults, limits and responses.

Source: https://edesy.in/docs/collect/api/campaigns

A reminder campaign takes four calls: create it, save its message, upload debtors, and start it. Create sets the caller ID, the optional number pool, attempt caps and calling hours. Values outside the allowed limits are corrected on the server. Uploading debtors and starting both need an approved KYC.

## Create a reminder campaign

```
POST /v1/collect/campaigns
```

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | Yes | | Campaign name |
| `from_number` | string | Yes | | Caller ID in E.164, owned by your workspace. Used for every call when no pool is set |
| `audio_type` | string | Yes | | `tts`. Becomes `audio` once the message is rendered |
| `tts_text` | string | No | | Message text; you can also set it with `/message` |
| `number_pool_id` | uuid | No | | Pool to rotate caller IDs from |
| `max_attempts_day` | integer | No | 2 | Attempts per debtor per day, 1 to 3 |
| `max_attempts_total` | integer | No | 6 | Total attempts per debtor |
| `min_gap_minutes` | integer | No | 180 | Minutes between attempts, at least 60 |
| `call_window_start` | integer | No | 540 | Minutes after midnight IST, 540 (09:00) or later |
| `call_window_end` | integer | No | 1200 | Minutes after midnight IST, 1260 (21:00) or earlier |
| `stop_on_connect` | boolean | No | `false` | Stop calling a debtor after their first answered call |
| `max_concurrent` | integer | No | 5 | Calls at the same time |

```bash
curl -X POST https://voice-api.edesy.in/v1/collect/campaigns \
  -H "Authorization: Bearer sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "November EMI reminders",
    "from_number": "+91XXXXXXXXXX",
    "audio_type": "tts",
    "number_pool_id": "POOL_ID",
    "max_attempts_day": 2,
    "max_attempts_total": 4,
    "min_gap_minutes": 180,
    "call_window_start": 600,
    "call_window_end": 1140
  }'
```

Returns `201` with the campaign in `data`, including `id`, `status` (`created`), `is_reminder: true` and the stored caps. Missing `name`, `from_number` or `audio_type` returns `400 name, from_number, and audio_type are required`.

## Save and render the message

```
POST /v1/collect/campaigns/:id/message
```

| Field | Type | Required | Description |
|---|---|---|---|
| `text` | string | Yes | The reminder text |
| `voice` | string | No | Voice ID; empty for the default voice |

The text is rendered to audio once and replayed on every call. Returns the updated campaign with `audio_type: "audio"` and `audio_url`. See [Reminder Message and Voice](https://edesy.in/docs/collect/campaigns/reminder-message-and-voice).

## Upload debtors

```
POST /v1/collect/campaigns/:id/contacts
```

| Field | Type | Required | Description |
|---|---|---|---|
| `contacts` | array | Yes | List of debtors |
| `contacts[].phone_number` | string | Yes | Debtor's number, E.164 recommended |
| `contacts[].name` | string | No | Name |
| `contacts[].due_date` | string | No | `YYYY-MM-DD` or RFC 3339 |
| `contacts[].amount_due` | number | No | Amount in rupees |

```json
{ "data": { "added": 2 } }
```

`added` counts debtors who will be dialled; numbers on your DNC list are stored as `suppressed` and not counted. Returns `403 KYC_NOT_APPROVED` without approved KYC. See [Upload Debtors](https://edesy.in/docs/collect/campaigns/upload-debtors-csv).

## Start

```
POST /v1/collect/campaigns/:id/start
```

Returns `{"status": "ok"}`. Fails if KYC is not approved, there are no debtors, no message is saved, or the wallet cannot fund calls. See [Start a Campaign](https://edesy.in/docs/collect/campaigns/start-a-campaign).

## Read results

Use `GET /v1/campaigns/:id` for counts and `GET /v1/campaigns/:id/contacts?page=1&per_page=50` for debtors. Each contact includes `id`, `phone_number`, `name`, `status`, `due_date`, `amount_due`, `attempts_total`, `next_attempt_at` and `payment_status`. Filter by call status with `?status=`.
