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

# Campaigns API

> Push one or more records to a RevRag campaign via the API and let the agent dial them per the campaign's schedule, retry, and concurrency configuration; then poll record and campaign status, or receive webhooks.

## Overview

A **campaign** in RevRag is a workspace-scoped definition that bundles an agent, a schedule, retry/follow-up rules, and concurrency limits.

This page documents the endpoints you use to push records into an already-running campaign and to track their status:

| Endpoint                  | Use when                                                                              |
| ------------------------- | ------------------------------------------------------------------------------------- |
| `POST /records/push-bulk` | Push one or more records into the campaign (a single record goes as a 1-element list) |
| `GET /records/{run_id}`   | Poll the status of a record you pushed (by the `run_id` returned on push)             |
| `GET /records`            | List a campaign's records, filterable by status                                       |
| `GET /status`             | Get the campaign's status + per-status record counts                                  |

The push endpoint returns a **`run_id`** for every record so you can correlate and poll each one.

The campaign itself — name, agent, schedule, end date, retry rules, concurrency — is configured and launched in the RevRag dashboard. By the time you call these APIs, the campaign should already be running. The dashboard's **Campaign → Settings** tab shows a copy-pasteable curl with your campaign's id pre-filled.

## Base URLs

* **Production**: `https://api.revrag.ai`
* **Staging**: `https://staging-api.revrag.ai`

## Authentication

All requests require a workspace-scoped API key in the `X-API-Key` header:

```
X-API-Key: YOUR_API_KEY
```

The workspace is resolved from the API key itself — you do not pass a workspace header. The campaign in the path must belong to the same workspace as the API key, otherwise the request returns `404 Not Found`.

## Before You Push

The campaign is fully created, configured, and started from the RevRag dashboard. From the API caller's side you only need:

* The **`campaign_id`** — copy it from the dashboard's Campaign → Settings tab (it's also in the URL of the campaign detail page).
* Your **`X-API-Key`** — issued for your workspace.
* The agent's **variable names**, if the agent expects per-call variables. Variable names are visible on the campaign's Settings tab.

## Endpoints

### 1. Push Records

**POST** `/v1/campaigns/{campaign_id}/records/push-bulk`

Queue one or more records for calling. Send a single record as a **1-element `records` array**. Each record is treated independently — the runner picks them up on its next polling cycle (typically within \~1 second) and dispatches each call subject to the campaign's daily time-window, attempt schedule, and concurrency cap. Invalid records are rejected individually (partial success), all in a single `200 OK`.

#### Path Parameters

| Parameter     | Type          | Required | Description                       |
| ------------- | ------------- | -------- | --------------------------------- |
| `campaign_id` | string (uuid) | Yes      | The campaign to push records into |

#### Header Parameters

| Parameter      | Type   | Required | Description                |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-Key`    | string | Yes      | Workspace-scoped API key   |
| `Content-Type` | string | Yes      | Must be `application/json` |

#### Request Body

```json theme={null}
{
  "records": [
    {
      "phone_number": "+919876543210",
      "variables": {
        "first_name": "Pankaj",
        "company": "Acme Inc."
      },
      "app_user_id": "user_12345",
      "app_session_id": "sess_abcdef"
    },
    {
      "phone_number": "+919876543211",
      "variables": { "first_name": "Riya" }
    }
  ]
}
```

#### Request Parameters

| Parameter | Type  | Required | Description                                                             |
| --------- | ----- | -------- | ----------------------------------------------------------------------- |
| `records` | array | Yes      | One or more records to push (use a 1-element array for a single record) |

Each record in `records`:

| Field            | Type   | Required | Description                                                                                                                             |
| ---------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `phone_number`   | string | Yes      | E.164 format recommended (`+919876543210`)                                                                                              |
| `variables`      | object | No       | Key-value pairs matching the agent's variable names. Sent into the agent's prompt at call time                                          |
| `app_user_id`    | string | No       | Your system's user id — a **top-level** correlation field (not inside `variables`). Returned top-level on the record and in webhooks    |
| `app_session_id` | string | No       | Your system's session id — a **top-level** correlation field (not inside `variables`). Returned top-level on the record and in webhooks |

#### Response

The response returns a **per-record result** for every record you submitted, in the same order as the `records` array (`results[i]` corresponds to `records[i]`). Each accepted record carries its own **`run_id`** so you can correlate every record to its created run and poll its status later (see [Get Record Status](#2-get-record-status)).

**Success (200 OK)**

```json theme={null}
{
  "accepted": 2,
  "rejected": 0,
  "results": [
    {
      "phone_number": "+919876543210",
      "accepted": true,
      "run_id": "00000000-0000-0000-0000-000000000001",
      "status": "queued",
      "error": null
    },
    {
      "phone_number": "+919876543211",
      "accepted": true,
      "run_id": "00000000-0000-0000-0000-000000000002",
      "status": "queued",
      "error": null
    }
  ]
}
```

**Partial success (200 OK)**

Invalid records are rejected individually — the valid ones are still created. A rejected record has `accepted: false`, `run_id: null`, and an `error` message.

```json theme={null}
{
  "accepted": 1,
  "rejected": 1,
  "results": [
    {
      "phone_number": "+919876543210",
      "accepted": true,
      "run_id": "00000000-0000-0000-0000-000000000001",
      "status": "queued",
      "error": null
    },
    {
      "phone_number": "not-a-number",
      "accepted": false,
      "run_id": null,
      "status": null,
      "error": "Invalid Indian phone number: not-a-number"
    }
  ]
}
```

#### Response Parameters

| Parameter  | Type   | Description                                                                        |
| ---------- | ------ | ---------------------------------------------------------------------------------- |
| `accepted` | number | How many records were inserted and queued for calling                              |
| `rejected` | number | How many were rejected (e.g. invalid phone number)                                 |
| `results`  | array  | Per-record outcome, in the same order as the submitted `records`. See fields below |

Each entry in `results`:

| Field          | Type                  | Description                                                                                               |
| -------------- | --------------------- | --------------------------------------------------------------------------------------------------------- |
| `phone_number` | string                | The phone number you submitted for this record                                                            |
| `accepted`     | boolean               | `true` if the record was created and queued, `false` if rejected                                          |
| `run_id`       | string (uuid) \| null | The created run id — present when `accepted: true`. Use it with [Get Record Status](#2-get-record-status) |
| `status`       | string \| null        | Initial status (`queued`) when accepted, `null` when rejected                                             |
| `error`        | string \| null        | Why the record was rejected — present when `accepted: false`                                              |

> Pushing a single record? Send a 1-element `records` array — you get that record's `run_id` back in `results[0]`.

#### cURL Example

```bash theme={null}
curl -X POST \
  "https://staging-api.revrag.ai/v1/campaigns/{campaign_id}/records/push-bulk" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "records": [
      {
        "phone_number": "+919876543210",
        "variables": { "first_name": "Pankaj" }
      },
      {
        "phone_number": "+919876543211",
        "variables": { "first_name": "Riya" }
      }
    ]
  }'
```

***

### 2. Get Record Status

**GET** `/v1/campaigns/{campaign_id}/records/{run_id}`

Fetch the current status and details of a pushed record, using the `run_id` you got back from the push (`results[].run_id`).

#### Path Parameters

| Parameter     | Type          | Required | Description                                  |
| ------------- | ------------- | -------- | -------------------------------------------- |
| `campaign_id` | string (uuid) | Yes      | The campaign the record belongs to           |
| `run_id`      | string (uuid) | Yes      | The record's run id (from the push response) |

#### Header Parameters

| Parameter   | Type   | Required | Description              |
| ----------- | ------ | -------- | ------------------------ |
| `X-API-Key` | string | Yes      | Workspace-scoped API key |

#### Response

**Success (200 OK)** — the full campaign record:

```json theme={null}
{
  "id": "00000000-0000-0000-0000-000000000001",
  "campaign_id": "11111111-1111-1111-1111-111111111111",
  "workspace_id": "22222222-2222-2222-2222-222222222222",
  "phone_number": "+919876543210",
  "status": "completed",
  "variables": { "first_name": "Pankaj" },
  "app_user_id": "user_12345",
  "app_session_id": "sess_abcdef",
  "attempt_count": 1,
  "max_attempts": 3,
  "call_history": [{ "call_id": "...", "status": "completed" }],
  "next_attempt_at": null,
  "dnc_at": null,
  "dnc_reason": null,
  "created_at": "2026-05-27T03:00:00.000000+00:00",
  "updated_at": "2026-05-27T03:05:12.000000+00:00"
}
```

See [Record Status Values](#record-status-values) for what each `status` means. The `call_history` array holds one entry per attempt, including the per-call `call_id` you can pass to [Get Call Status](#get-call-status) for the transcript and recording.

#### cURL Example

```bash theme={null}
curl -X GET \
  "https://staging-api.revrag.ai/v1/campaigns/{campaign_id}/records/{run_id}" \
  -H "X-API-Key: YOUR_API_KEY"
```

***

### 3. List Records

**GET** `/v1/campaigns/{campaign_id}/records`

List the campaign's records, optionally filtered by status — useful for polling everything in a given state (e.g. all `queued`, or all `completed`).

#### Query Parameters

| Parameter   | Type   | Required | Description                                                                             |
| ----------- | ------ | -------- | --------------------------------------------------------------------------------------- |
| `status`    | string | No       | Filter by [record status](#record-status-values) (e.g. `queued`, `completed`, `failed`) |
| `page`      | number | No       | 1-based page number (default `1`)                                                       |
| `page_size` | number | No       | Records per page (default `50`, max `200`)                                              |

#### Response

**Success (200 OK)**

```json theme={null}
{
  "items": [
    {
      "id": "00000000-0000-0000-0000-000000000001",
      "phone_number": "+919876543210",
      "status": "completed",
      "attempt_count": 1,
      "max_attempts": 3
    }
  ],
  "total": 100,
  "has_more": true,
  "page": 1,
  "page_size": 50
}
```

Each item has the same fields as a single record (see [Get Record Status](#2-get-record-status)).

#### cURL Example

```bash theme={null}
curl -X GET \
  "https://staging-api.revrag.ai/v1/campaigns/{campaign_id}/records?status=completed&page=1&page_size=50" \
  -H "X-API-Key: YOUR_API_KEY"
```

***

### 4. Get Campaign Status

**GET** `/v1/campaigns/{campaign_id}/status`

Get the campaign's lifecycle status plus aggregate per-status record counts — a quick way to track overall progress without listing every record.

#### Response

**Success (200 OK)**

```json theme={null}
{
  "campaign_id": "11111111-1111-1111-1111-111111111111",
  "status": "running",
  "stats": {
    "total_records": 100,
    "queued": 18,
    "in_progress": 4,
    "completed": 70,
    "failed": 3,
    "dnc": 2,
    "rescheduled": 1,
    "retry_pending": 2,
    "follow_up_pending": 0
  }
}
```

#### Response Parameters

| Parameter     | Type          | Description                                                                                                                                            |
| ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `campaign_id` | string (uuid) | The campaign                                                                                                                                           |
| `status`      | string        | Campaign lifecycle status (`running`, `paused`, `completed`, `cancelled`, …)                                                                           |
| `stats`       | object        | Per-status record counts (`total_records`, `queued`, `in_progress`, `completed`, `failed`, `dnc`, `rescheduled`, `retry_pending`, `follow_up_pending`) |

#### cURL Example

```bash theme={null}
curl -X GET \
  "https://staging-api.revrag.ai/v1/campaigns/{campaign_id}/status" \
  -H "X-API-Key: YOUR_API_KEY"
```

***

## How Records are Called

Pushing a record does not always trigger an immediate dial. After your push is accepted, the campaign runner picks up queued records each cycle and applies the campaign's configured gates (all set in the dashboard):

| Gate                                                                       | Effect on your record                     |
| -------------------------------------------------------------------------- | ----------------------------------------- |
| Outside the campaign's daily calling window                                | Stays queued until the window opens       |
| Day not in the campaign's active days (e.g. weekend on a Mon-Fri campaign) | Queued until the next active day's window |
| First-attempt schedule is later than today                                 | Queued until that day                     |
| Campaign's max concurrency reached                                         | Queued until a slot frees                 |
| Phone is in the workspace's Do-Not-Call list                               | Marked `dnc`, no call placed              |

When all gates are open, dispatch is typically within \~1 second of the push. See [Record Status Values](#record-status-values) for what each status means in flight.

If the campaign has **Dial first attempt immediately** enabled (a dashboard setting), the **first** attempt skips the *first-attempt schedule* gate and is dialed as soon as the record is pushed — but still only within the calling window and the concurrency/DNC gates. Retries and follow-ups (attempts 2+) follow all the gates above as normal.

If you push to a campaign that has finished (past its end date) or has been cancelled/paused-and-stopped from the dashboard, the API returns `400`. The campaign must be in an active state on the dashboard to accept pushes.

## Get Call Status

**GET** `/v1/campaigns/trigger/status/{call_id}`

Fetch the status, transcript, recording URL, summary, and extracted variables for a specific call. The `call_id` for a pushed record is available in the record's `call_history` (via [Get Record Status](#2-get-record-status)), delivered via webhook (see below), or visible on the campaign's Records tab in the dashboard. The push response itself returns the record's **`run_id`** (not the per-call `call_id`) — the call is placed asynchronously by the campaign runner, so poll the record (or wait for the webhook) to get the `call_id`.

#### Path Parameters

| Parameter | Type          | Required | Description                                                          |
| --------- | ------------- | -------- | -------------------------------------------------------------------- |
| `call_id` | string (uuid) | Yes      | The call id, obtained from the webhook payload or from the dashboard |

#### Header Parameters

| Parameter   | Type   | Required | Description              |
| ----------- | ------ | -------- | ------------------------ |
| `X-API-Key` | string | Yes      | Workspace-scoped API key |

#### Response

**Success (200 OK)**

```json theme={null}
{
  "call_status": "completed",
  "call_id": "123e4567-e89b-12d3-a456-426614174000",
  "agent_id": "33333333-3333-3333-3333-333333333333",
  "from_number": "+911140000000",
  "to_number": "+919876543210",
  "summary": "Customer confirmed the appointment for Friday.",
  "start_time": "2026-05-27T03:05:00+00:00",
  "end_time": "2026-05-27T03:06:12+00:00",
  "duration": 72,
  "disconnection_reason": "agent_hangup",
  "recording_url": "https://.../recording.mp3",
  "transcription": {
    "messages": [
      { "role": "assistant", "content": "Hi Pankaj, calling about your appointment…" },
      { "role": "user", "content": "Yes, Friday works." }
    ]
  },
  "custom_variables": [
    { "key": "appointment_confirmed", "type": "boolean", "value": true }
  ],
  "variables_fields": { "first_name": "Pankaj" },
  "campaign_id": "11111111-1111-1111-1111-111111111111",
  "attempt_no": 1,
  "app_user_id": "user_12345",
  "app_session_id": "sess_abcdef"
}
```

#### Response Parameters

| Field                            | Type                  | Description                                                                       |
| -------------------------------- | --------------------- | --------------------------------------------------------------------------------- |
| `call_status`                    | string                | Final call status (e.g. `completed`, `failed`)                                    |
| `call_id`                        | string (uuid)         | The call this status is for                                                       |
| `agent_id`                       | string (uuid)         | The agent that placed the call                                                    |
| `from_number` / `to_number`      | string                | Caller / callee numbers                                                           |
| `summary`                        | string                | LLM-generated call summary (when a transcript exists)                             |
| `start_time` / `end_time`        | string \| null        | ISO-8601 timestamps                                                               |
| `duration`                       | number                | Call duration in seconds                                                          |
| `disconnection_reason`           | string \| null        | Normalized hangup reason                                                          |
| `recording_url`                  | string \| null        | Presigned recording URL (for transcribed calls)                                   |
| `transcription`                  | object \| null        | Turn-by-turn messages                                                             |
| `custom_variables`               | array                 | Post-call variables extracted by the agent (`key`, `type`, `value`)               |
| `variables_fields`               | object \| null        | The `variables` you sent on push (correlation IDs are surfaced top-level instead) |
| `campaign_id`                    | string (uuid) \| null | Campaign the call belongs to (null for non-campaign calls)                        |
| `attempt_no`                     | number \| null        | Which attempt this call was                                                       |
| `app_user_id` / `app_session_id` | string \| null        | Your correlation IDs (top-level), if you sent them                                |

#### cURL Example

```bash theme={null}
curl -X GET \
  "https://staging-api.revrag.ai/v1/campaigns/trigger/status/123e4567-e89b-12d3-a456-426614174000" \
  -H "X-API-Key: YOUR_API_KEY"
```

## Webhook Notifications

Instead of polling for status, configure a webhook URL to receive a `POST` notification when each call ends. Webhooks fire for every call placed by the agent — including calls dispatched from campaigns pushed via this API.

#### Configuration

Contact **[contact@revrag.ai](mailto:contact@revrag.ai)** to configure your webhook URL and signing secret.

#### Security & Headers

Campaign webhooks use the **same** signing scheme as every RevRag webhook — HMAC-SHA256 computed over `"{timestamp}.{raw_body}"` with your shared secret. Each request carries:

| Header                | Purpose                                       |
| --------------------- | --------------------------------------------- |
| `X-Webhook-Event`     | Event type (`call.ended`)                     |
| `X-Webhook-Timestamp` | Unix timestamp (seconds) the webhook was sent |
| `X-Webhook-Signature` | `t=<timestamp>,v1=<hmac_sha256_hex>`          |
| `X-Webhook-ID`        | Unique id for deduplication                   |

See [Webhook Security](./webhook-security) for the full verification steps and Python / Node.js examples.

#### Payload

The webhook body is the same JSON shape as the [Get Call Status](#get-call-status) response. For calls placed from a campaign it also carries **`campaign_id`**, **`attempt_no`**, and your top-level correlation IDs **`app_user_id`** / **`app_session_id`**. The `variables` you sent are echoed back as **`variables_fields`** (with the correlation IDs kept out of it).

```json theme={null}
{
  "call_status": "completed",
  "call_id": "123e4567-e89b-12d3-a456-426614174000",
  "agent_id": "33333333-3333-3333-3333-333333333333",
  "from_number": "+911140000000",
  "to_number": "+919876543210",
  "summary": "Customer confirmed the appointment for Friday.",
  "start_time": "2026-05-27T03:05:00+00:00",
  "end_time": "2026-05-27T03:06:12+00:00",
  "duration": 72,
  "disconnection_reason": "agent_hangup",
  "recording_url": "https://.../recording.mp3",
  "transcription": {
    "messages": [{ "role": "assistant", "content": "Hello…" }]
  },
  "custom_variables": [
    { "key": "appointment_confirmed", "type": "boolean", "value": true }
  ],
  "variables_fields": { "first_name": "Pankaj" },
  "campaign_id": "11111111-1111-1111-1111-111111111111",
  "attempt_no": 1,
  "app_user_id": "user_12345",
  "app_session_id": "sess_abcdef"
}
```

## Record Status Values

| Status              | Description                                                                                          |
| ------------------- | ---------------------------------------------------------------------------------------------------- |
| `queued`            | Accepted, waiting for the runner to pick it up                                                       |
| `in_progress`       | Call is being placed or is live                                                                      |
| `completed`         | Call finished — see `call_history` on the record for details                                         |
| `failed`            | Call attempt failed (e.g. telephony error). Will retry if attempts remain and retry conditions match |
| `retry_pending`     | Waiting for the next retry attempt at `next_attempt_at`                                              |
| `follow_up_pending` | Waiting for a follow-up attempt scheduled by post-call logic                                         |
| `rescheduled`       | Caller requested a callback — will dispatch at the requested time                                    |
| `dnc`               | Phone added to Do-Not-Call list, no further attempts                                                 |

## Common Errors

| Status                     | When                                                                                                                                                                                                                |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`          | Campaign is not currently accepting pushes (e.g. already finished or cancelled on the dashboard). A malformed `phone_number` is **not** a request-level error — it's rejected per-record in `results` (still `200`) |
| `401 Unauthorized`         | Missing or invalid `X-API-Key` header                                                                                                                                                                               |
| `404 Not Found`            | `campaign_id` doesn't exist in the API key's workspace, or `campaign_id` isn't a valid UUID                                                                                                                         |
| `422 Unprocessable Entity` | Request body doesn't match the schema (missing `phone_number`, malformed JSON, etc.)                                                                                                                                |

**Example error body**

```json theme={null}
{
  "detail": "Cannot push records: campaign is completed."
}
```

## Support

For API keys, campaign setup, or any technical issue, contact **[contact@revrag.ai](mailto:contact@revrag.ai)**.
