# Create an AI field

`POST https://api.versionseven.ai/v1/campaigns/{campaign_id}/ai-fields`

Adds an AI field to the campaign. `field_name` starts with a letter and uses only letters, digits and underscores, up to 40 characters; it can't be a name the sender fills from the lead record (`first_name`, `last_name`, `company`, `title`, `employee_count`, `industry`, `linkedin_from_name`, `email_from_name`). Names are compared without regard to case, and an active field with the same name on the campaign answers `409 AI_FIELD_EXISTS`.

`ai_instructions` say what to write for each lead. `fallback_value` is required: it's what a lead gets when personalization fails, so a message still reads well. `data_sources` is `linkedin`, `website` or both, and defaults to both.

> **Note:** Generating a field for each lead spends your organization's credits when the campaign sends. [Preview the campaign](https://docs.versionseven.ai/api-reference/campaigns/preview-campaign) renders every AI field at its fallback value, which costs nothing.

- Required scope: `campaigns:write`

## Request

**cURL**

```bash
curl -X POST "https://api.versionseven.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/ai-fields" \
  -H "Authorization: Bearer $VICTORIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "field_name": "recent_news",
  "field_description": "One recent, specific thing the company did",
  "ai_instructions": "In one short sentence, name something specific the company announced or shipped in the last few months, from its website. No praise, no adjectives.",
  "fallback_value": "the work your team is doing",
  "data_sources": [
    "website"
  ]
}'
```

**Node.js**

```javascript
const response = await fetch("https://api.versionseven.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/ai-fields", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.VICTORIA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "field_name": "recent_news",
    "field_description": "One recent, specific thing the company did",
    "ai_instructions": "In one short sentence, name something specific the company announced or shipped in the last few months, from its website. No praise, no adjectives.",
    "fallback_value": "the work your team is doing",
    "data_sources": [
      "website"
    ]
  }),
});

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

**Python**

```python
import os

import requests

response = requests.post(
    "https://api.versionseven.ai/v1/campaigns/550e8400-e29b-41d4-a716-446655440000/ai-fields",
    headers={
        "Authorization": f"Bearer {os.environ['VICTORIA_API_KEY']}",
    },
    json={
        "field_name": "recent_news",
        "field_description": "One recent, specific thing the company did",
        "ai_instructions": "In one short sentence, name something specific the company announced or shipped in the last few months, from its website. No praise, no adjectives.",
        "fallback_value": "the work your team is doing",
        "data_sources": [
            "website",
        ],
    },
)
data = response.json()
```

## Headers

- `Authorization` (string, required): `Bearer` followed by a space and your API key, for example `Bearer vk_…`.

## Path parameters

- `campaign_id` (string · uuid, required): ID of the campaign.

## Request body

- `field_name` (string, required, at most 40 characters): Used as {field\_name} in messages. Letters, digits, underscores.
- `ai_instructions` (string, required, at most 4,000 characters): What to write for each lead, and from what
- `fallback_value` (string, required, at most 300 characters): Used when personalisation fails, so a lead still gets a sensible message
- `data_sources` (array of strings or null, optional): Where to research each lead. Defaults to both.
- `field_description` (string or null, optional, at most 500 characters)

## Response

### `201`

```json
{
  "message": "string",
  "ai_field": {
    "id": "a5614527-0ce6-43be-bd1d-d012b7394867",
    "field_name": "string",
    "ai_instructions": "string",
    "created_at": "2026-01-14T19:30:00+00:00",
    "data_sources": [
      "string"
    ],
    "fallback_value": "string",
    "field_description": "string",
    "is_active": true,
    "updated_at": "2026-01-14T19:30:00+00:00"
  },
  "success": true
}
```

- `message` (string, required)
- `ai_field` (object or null, optional): A per-lead variable a model writes before each message, e.g. {recent\_post}.
  - `id` (string, required)
  - `field_name` (string, required): The variable's name, used as `{field_name}` in step copy.
  - `ai_instructions` (string, required): What to write for each lead, and from what.
  - `created_at` (string · date-time or null, optional)
  - `data_sources` (array of strings or null, optional): Where each lead is researched: `linkedin`, `website` or both.
  - `fallback_value` (string or null, optional): What a lead gets when personalization fails, so its message still reads well.
  - `field_description` (string or null, optional)
  - `is_active` (boolean, optional, default `true`): Only active fields are generated. Set `false` to stop using a field; there's no delete.
  - `updated_at` (string · date-time or null, optional)
- `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`. |
| 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. |
| 404 | `CAMPAIGN_NOT_FOUND` | No campaign with this ID exists in your organization. |
| 404 | `NOT_FOUND` | No endpoint matches the path. A resource ID in the path that isn't a valid UUID also answers `NOT_FOUND`. |
| 409 | `AI_FIELD_EXISTS` | An active AI field with this name already exists on the campaign. Names are compared without regard to case. |
| 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 | `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`: seconds to wait before retrying. |
