# Create a campaign

`POST https://api.versionseven.ai/v1/campaigns`

Creates a campaign in draft, with `is_active: false`. Add its sequence, AI fields, senders and leads, check it with [Get the activation preflight](https://docs.versionseven.ai/api-reference/campaigns/get-campaign-preflight), then activate it with [Update a campaign](https://docs.versionseven.ai/api-reference/campaigns/update-campaign). [Set up a campaign](https://docs.versionseven.ai/guides/set-up-a-campaign) walks through the order.

An optional `sequence` can be sent now. A sequence is validated before it's saved:

- `variation_a` is required and `variation_b` is optional. Each is a list of steps.
- Step types are `view_profile`, `linkedin_connection`, `conditional`, `linkedin_message` and `email`.
- A `linkedin_connection` step must be followed immediately by a `conditional` step with `branches.yes` and `branches.no`. Conditionals nest at most two deep.
- `linkedin_message` steps are only allowed inside a `yes` branch.
- `email` steps need `subject` and `content`.
- `delay` is a number of days from 0 to 90.
- Every step needs a unique integer `id`, and a sequence has at most 40 steps.

The easiest valid starting point is a [sequence template](https://docs.versionseven.ai/api-reference/reference/list-sequence-templates).

- Required scope: `campaigns:write`
- Accepts an `Idempotency-Key` header, which makes retries safe. See [Idempotency](https://docs.versionseven.ai/guides/idempotency).

## Request

**cURL**

```bash
curl -X POST "https://api.versionseven.ai/v1/campaigns" \
  -H "Authorization: Bearer $VICTORIA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "name": "Q3 outbound: heads of sales",
  "description": "Three-email sequence",
  "sequence": {
    "variation_a": [
      {
        "id": 1,
        "type": "email",
        "subject": "Question about {company}",
        "content": "Hi {first_name},\n\nAre you the right person to talk to about how {company} books sales meetings?\n\nThanks,\nJane",
        "delay": 2
      },
      {
        "id": 2,
        "type": "email",
        "subject": "Re: Question about {company}",
        "content": "Hi {first_name},\n\nFollowing up on my last note. Would a short call next week be useful?\n\nJane",
        "delay": 3
      },
      {
        "id": 3,
        "type": "email",
        "subject": "Closing the loop",
        "content": "Hi {first_name},\n\nI haven'\''t heard back, so I'\''ll assume the timing isn'\''t right. If that changes, just reply to this email.\n\nJane",
        "delay": 0
      }
    ],
    "variation_b": []
  }
}'
```

**Node.js**

```javascript
const response = await fetch("https://api.versionseven.ai/v1/campaigns", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.VICTORIA_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    "name": "Q3 outbound: heads of sales",
    "description": "Three-email sequence",
    "sequence": {
      "variation_a": [
        {
          "id": 1,
          "type": "email",
          "subject": "Question about {company}",
          "content": "Hi {first_name},\n\nAre you the right person to talk to about how {company} books sales meetings?\n\nThanks,\nJane",
          "delay": 2
        },
        {
          "id": 2,
          "type": "email",
          "subject": "Re: Question about {company}",
          "content": "Hi {first_name},\n\nFollowing up on my last note. Would a short call next week be useful?\n\nJane",
          "delay": 3
        },
        {
          "id": 3,
          "type": "email",
          "subject": "Closing the loop",
          "content": "Hi {first_name},\n\nI haven't heard back, so I'll assume the timing isn't right. If that changes, just reply to this email.\n\nJane",
          "delay": 0
        }
      ],
      "variation_b": []
    }
  }),
});

const data = await response.json();
```

**Python**

```python
import os
import uuid

import requests

response = requests.post(
    "https://api.versionseven.ai/v1/campaigns",
    headers={
        "Authorization": f"Bearer {os.environ['VICTORIA_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "name": "Q3 outbound: heads of sales",
        "description": "Three-email sequence",
        "sequence": {
            "variation_a": [
                {
                    "id": 1,
                    "type": "email",
                    "subject": "Question about {company}",
                    "content": "Hi {first_name},\n\nAre you the right person to talk to about how {company} books sales meetings?\n\nThanks,\nJane",
                    "delay": 2,
                },
                {
                    "id": 2,
                    "type": "email",
                    "subject": "Re: Question about {company}",
                    "content": "Hi {first_name},\n\nFollowing up on my last note. Would a short call next week be useful?\n\nJane",
                    "delay": 3,
                },
                {
                    "id": 3,
                    "type": "email",
                    "subject": "Closing the loop",
                    "content": "Hi {first_name},\n\nI haven't heard back, so I'll assume the timing isn't right. If that changes, just reply to this email.\n\nJane",
                    "delay": 0,
                },
            ],
            "variation_b": [],
        },
    },
)
data = response.json()
```

## Headers

- `Authorization` (string, required): `Bearer` followed by a space and your API key, for example `Bearer vk_…`.
- `Idempotency-Key` (string, optional, at most 255 characters): Any unique string, such as a UUID, that makes retrying this request safe. A repeat with the same key for at least 24 hours returns the original response instead of doing the work again. Use a new key for each distinct request.

## Request body

- `name` (string, required, at most 2,000 characters, example `"Q3 Proof First Outreach"`): Campaign name
- `description` (string or null, optional, at most 2,000 characters, default `""`): Campaign description
- `sequence` (object or null, optional): Optional full sequence object (variation\_a / variation\_b step arrays); validated against structural rules

## Response

### `201`

```json
{
  "campaign": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Q1 Enterprise Outreach",
    "is_active": true,
    "created_at": "2024-01-15T10:30:00+00:00",
    "updated_at": "2024-01-20T14:45:00+00:00",
    "ab_testing_enabled": true,
    "custom_fields": null,
    "description": "Targeting enterprise accounts in the tech sector",
    "enabled_accounts": [
      "string"
    ],
    "lead_database_filters": {},
    "refill_policy": {},
    "responder_enabled": true,
    "sequence": {
      "variation_a": [
        {
          "id": 1,
          "type": "email",
          "subject": "Question about {company}",
          "content": "Hi {first_name},\n\nAre you the right person to talk to about how {company} books sales meetings?\n\nThanks,\nJane",
          "delay": 2
        },
        {
          "id": 2,
          "type": "email",
          "subject": "Re: Question about {company}",
          "content": "Hi {first_name},\n\nFollowing up on my last note. Would a short call next week be useful?\n\nJane",
          "delay": 3
        },
        {
          "id": 3,
          "type": "email",
          "subject": "Closing the loop",
          "content": "Hi {first_name},\n\nI haven't heard back, so I'll assume the timing isn't right. If that changes, just reply to this email.\n\nJane",
          "delay": 0
        }
      ],
      "variation_b": []
    },
    "traffic_split": 50
  },
  "campaign_id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Campaign created in draft state",
  "success": true
}
```

- `campaign` (object or null, optional): The created campaign
  - `id` (string, required, example `"550e8400-e29b-41d4-a716-446655440000"`): Campaign UUID
  - `name` (string, required, example `"Q1 Enterprise Outreach"`): Campaign name
  - `is_active` (boolean, required, example `true`): Whether the campaign is currently active
  - `created_at` (string · date-time, required, example `"2024-01-15T10:30:00+00:00"`): When the campaign was created
  - `updated_at` (string · date-time, required, example `"2024-01-20T14:45:00+00:00"`): When the campaign was last updated
  - `ab_testing_enabled` (boolean or null, optional, example `true`): Whether A/B testing is enabled for message variations
  - `custom_fields` (any or null, optional): Custom fields — object on newer campaigns, list on legacy ones
  - `description` (string or null, optional, example `"Targeting enterprise accounts in the tech sector"`): Campaign description
  - `enabled_accounts` (array of strings or null, optional): List of connected account IDs used for outreach
  - `lead_database_filters` (object or null, optional): Lead-database search filters defining this campaign's ICP (same shape the lead database search accepts)
  - `refill_policy` (object or null, optional): Automatic lead-refill settings for the campaign: `enabled`, `min_backlog`, `target_backlog`, `max_add_per_run`, `max_tokens_per_run`, `cooldown_hours` and `mode`.
  - `responder_enabled` (boolean or null, optional, example `true`): Whether AI auto-responder is enabled
  - `sequence` (any or null, optional): Campaign sequence: an object with variation\_a and variation\_b message templates, or an empty list on a campaign with no sequence yet
  - `traffic_split` (integer or null, optional, example `50`): Traffic split percentage for A/B testing (0-100)
- `campaign_id` (string or null, optional): UUID of the created campaign
- `message` (string, optional, default `"Campaign created in draft state"`)
- `success` (boolean, optional, default `true`)

## Errors

Errors share one JSON body: `success`, `error`, `message`, optional `details`, and `request_id`. For example:

```json
{
  "success": false,
  "error": "UNAUTHORIZED",
  "message": "Invalid or inactive API key",
  "request_id": "734d11a1-54d4-414c-9e8d-4e1ada977db4"
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | The request didn't match the endpoint's schema: a missing or malformed field, a bad query parameter, or a value out of range. `details.errors` lists each failing field with its `field`, `message` and `type`. |
| 400 | `SEQUENCE_VALIDATION_FAILED` | The sequence breaks a structural rule, such as an unknown step type or a `linkedin_connection` step without a `conditional` after it. The message lists every rule that failed. |
| 401 | `UNAUTHORIZED` | The `Authorization` header is missing or malformed, or the API key is unknown or has been deactivated. |
| 401 | `API_KEY_EXPIRED` | The API key is past its expiry date. Create a new key in the Victoria AI app. |
| 403 | `INSUFFICIENT_SCOPE` | The API key doesn't have the scope this endpoint requires, such as `leads:write`. |
| 403 | `ORGANIZATION_DEACTIVATED` | The organization that owns this API key has been deactivated. |
| 409 | `IDEMPOTENCY_KEY_REUSED` | This `Idempotency-Key` was already used with a different request. Use a new key for each distinct request. |
| 409 | `IDEMPOTENCY_IN_PROGRESS` | The first request with this `Idempotency-Key` is still running. Retry after the `Retry-After` interval. |
| 413 | `PAYLOAD_TOO_LARGE` | The request body is larger than 1 MB. |
| 429 | `RATE_LIMITED` | Too many requests for this API key: more than 100 a minute to one endpoint, or 600 a minute in total. Retry after the number of seconds in the `Retry-After` header. See [Rate limits](https://docs.versionseven.ai/guides/rate-limits). |
| 500 | `ORG_HAS_NO_MEMBERS` | The organization has no members, so the campaign can't be created. Contact support. |
| 500 | `INTERNAL_ERROR` | Something failed on our side. The response never includes internal details; quote its `request_id` when you contact support. |
| 503 | `UPSTREAM_TIMEOUT` | A service the API depends on timed out. The request is safe to retry. |
| 503 | `AUTH_UNAVAILABLE` | The API key couldn't be checked because the authentication service was unavailable. The request is safe to retry. |

## Response headers

| Header | Description |
| --- | --- |
| `X-Request-ID` | Correlation ID for the request, also returned as `request_id` in error bodies. Send your own `X-Request-ID`, up to 128 letters, digits, `.`, `_`, `:` or `-`, and it's used instead. |
| `RateLimit-Limit` | Requests allowed to this endpoint per minute. |
| `RateLimit-Remaining` | Requests you can still send to this endpoint right now. |
| `RateLimit-Reset` | Seconds until the endpoint's full limit is available again. |
| `Retry-After` | On a `429`, and on `409 IDEMPOTENCY_IN_PROGRESS`: seconds to wait before retrying. |
| `Idempotent-Replay` | `true` when the response is a stored replay of an earlier request with the same `Idempotency-Key`. |
