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

# Company campaigns

> Partner API for company (global) outreach campaigns and leads — list, create, update, and manage campaign participants.

Company campaigns are **workspace-wide** automation flows stored as flows with `accessLevel: COMPANY`. Use them for outbound calling workflows: scheduling, interviews, interest checks, assessment reminders, and similar global campaigns—with IVR or AI call scripts, defined call times, and optional SMS.

Use these endpoints to **create and update campaigns**, **inspect** their public configuration, and **add or remove leads** (candidates on the campaign) using the same Bearer API key model as the rest of the Partner API.

**Base path:** `/v1/partner/campaigns`\
**Company sites (needed to create a campaign):** `/v1/partner/company-sites`\
**Authentication:** `Authorization: Bearer YOUR_API_KEY` (same as [API keys](/authentication/api-keys))

<Note>
  Set **`purposeType`** and related fields to match the kind of campaign you are building (same concepts as creating a **global / company campaign** in the TapTalent app). Optional **`GET /campaigns`** query parameters let you filter by **`status`** or **`purposeType`**.
</Note>

***

## Concepts

| Term               | Meaning                                                                                                                                                                         |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Campaign**       | A row in `client_flow_details` with `accessLevel: COMPANY` — what the product calls a global / company campaign.                                                                |
| **Campaign id**    | Numeric `id` of the flow; use it as `:campaignId` in URLs.                                                                                                                      |
| **Lead**           | A person on the campaign. The Partner API exposes **`leadId`**, which is the same **`talentId`** used in internal flow APIs (`client_flow_talents` / `client_flow_candidates`). |
| **Response shape** | Responses return the partner-facing campaign and lead fields documented in this page.                                                                                           |

**Typical integration flow**

1. `GET /v1/partner/company-sites` → pick a **`companySiteId`** assigned to the API key user.
2. `POST /v1/partner/campaigns` → create a campaign with the appropriate **`purposeType`** and payload for that campaign.
3. `POST /v1/partner/campaigns/:campaignId/leads` → add one or many candidates.
4. `GET /v1/partner/campaigns/:campaignId/leads` → poll status labels until your automation is done.

***

## List campaigns

### `GET /campaigns`

Returns **company-scoped** campaigns (`accessLevel: COMPANY`) for the **company** tied to the API key. Results are ordered with **newest campaign id first**, then paginated.

**Query parameters**

| Parameter     | Type    | Description                                                            |
| ------------- | ------- | ---------------------------------------------------------------------- |
| `status`      | string  | Exact match on campaign `status` (e.g. `RUNNING`).                     |
| `purposeType` | string  | Exact match on `purposeType` (e.g. `ASSESSMENT_REMINDER`, `SCHEDULE`). |
| `page`        | integer | Page number, default `1`, minimum `1`.                                 |
| `limit`       | integer | Page size, default `20`, maximum `100`.                                |

**Example: filter by purpose and paginate**

```bash theme={null}
curl -s -X GET \
  "https://partner-api.taptalent.io/v1/partner/campaigns?purposeType=SCHEDULE&status=RUNNING&page=1&limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Accept: application/json"
```

**Example response**

```json theme={null}
{
  "campaigns": [
    {
      "id": 101,
      "title": "Assessment reminders — Q2",
      "status": "RUNNING",
      "purposeType": "ASSESSMENT_REMINDER",
      "timezone": "America/New_York",
      "companySiteId": 12,
      "accessLevel": "COMPANY",
      "createdAt": "2025-04-01T12:00:00.000Z",
      "updatedAt": "2025-04-10T09:30:00.000Z",
      "callSettings": { "callType": "IVR" },
      "assessment": {
        "assessmentTestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "assessmentName": "Technical screen",
        "jobTitle": "Senior Engineer",
        "autoAddInvitedCandidates": false,
        "assessmentFirstReminderDelayHours": 24
      }
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "totalPages": 1
  }
}
```

Depending on `purposeType`, optional blocks such as `assessment` may appear in list items; `callSettings` and core fields are included when present in the mapped summary.

***

## Get one campaign

### `GET /campaigns/:campaignId`

Returns a campaign detail object with high-level schedule, IVR/AI call settings, opening/closing messages, and **purpose-specific** blocks when applicable (for example an `assessment` object for assessment-related campaigns).

* **404** if the id is invalid, deleted, not in the API key’s company, or **not** a company (`COMPANY`) campaign — this avoids leaking whether an id exists in another workspace.

**Example request**

```bash theme={null}
curl -s -X GET \
  "https://partner-api.taptalent.io/v1/partner/campaigns/101" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example response (truncated)**

```json theme={null}
{
  "campaign": {
    "id": 101,
    "title": "Assessment reminders — Q2",
    "status": "RUNNING",
    "purposeType": "ASSESSMENT_REMINDER",
    "timezone": "America/New_York",
    "companySiteId": 12,
    "createdAt": "2025-04-01T12:00:00.000Z",
    "updatedAt": "2025-04-10T09:30:00.000Z",
    "callSettings": {
      "callType": "IVR",
      "aiCallScript": ""
    },
    "schedule": {
      "callTimeDisplay": "Apr 15, 2025 10:00 AM",
      "callSchedule": {
        "timeBasis": "UTC",
        "startTimeLocal": null,
        "startTimeUtc": "2025-04-15T14:00:00.000Z"
      },
      "followUpRetries": [
        { "time": "2025-04-16T14:00:00.000Z", "delayDays": 1 }
      ]
    },
    "messages": {
      "openingMessage": "Hi, this is a reminder from Acme about your assessment.",
      "closingMessage": "Thank you. Goodbye."
    },
    "assessment": {
      "assessmentTestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "assessmentName": "Technical screen",
      "jobTitle": "Senior Engineer",
      "autoAddInvitedCandidates": false,
      "assessmentReminderSmsTemplate": "Reminder: complete your assessment.",
      "assessmentPostCallSmsTemplate": "Thanks for taking the call.",
      "assessmentFirstReminderDelayHours": 24
    }
  }
}
```

***

## Create campaign

### `POST /campaigns`

Creates a **new** company campaign. The request body is validated with **strict keys** (`stripUnknown: true`); extra properties are rejected.

**Rules**

* **`purposeType`** identifies which campaign you are creating (for example scheduling, virtual interviews, interest checks, or assessment reminders). **Payload requirements depend on the purpose**—mirror the same fields you would use when creating that campaign type in the app (global / company campaign).
* **Partner API allowlist:** by default, only **`ASSESSMENT_REMINDER`** is accepted for `POST /campaigns` (and related updates). Your workspace may support additional `purposeType` values when configured server-side. The tables below describe **payload expectations for each campaign kind** (same as the in-app global campaign creator), even if a given type is not yet enabled for your API key.
* **`companySiteId`** must be a site **linked to the API key user** (use `GET /company-sites` first). The server applies the same checks as the in-app global campaign creator.
* **`phoneNumber`** must be a **verified** outbound number on the company (present in your company caller ID settings with status **VERIFIED**). If the number is missing or not verified, the API returns a **400** with a clear message.
* **Schedule:** for campaign types that use bulk calling, supply enough data for a valid first-call schedule. Common patterns:
  * **UTC-based:** `callStartTimeUtc` (and optionally `retryScheduleUtc`) **or**
  * **Local display + retries:** `callTime` and/or `followUpRetries` (timezone-aware scheduling, same behavior as in the app).
* **Follow-up retries (auto-fill):** If you omit **`followUpRetries`** (local schedule) or **`retryScheduleUtc`** (UTC schedule), the server **fills four retry slots** using the **same rules as the TapTalent app**: offsets **+60, +90, +90, and +90 minutes** after the previous slot, with calls constrained to **9:00 AM–9:00 PM** (if a slot would fall after 9:00 PM, it moves to **9:00 AM** on a later day, matching the product UI).
* **Assessment reminder job title:** For **`ASSESSMENT_REMINDER`**, if **`jobDetails.jobTitle`** is omitted or empty, the server uses **`jobDetails.assessmentName`**, then the campaign **`title`**, in that order. At least one of these must yield a non-empty label.

**Common body fields** (always review validation **responses** for your chosen `purposeType`; required flags vary by type.)

| Field              | Required | Description                                                                                                                                             |
| ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title`            | Yes      | Campaign name (1–500 characters).                                                                                                                       |
| `timezone`         | Yes      | IANA timezone string (e.g. `America/Chicago`).                                                                                                          |
| `companySiteId`    | Yes      | Integer or string; from `GET /company-sites`.                                                                                                           |
| `phoneNumber`      | Yes      | Outbound caller ID; must be a **verified** number on the company (same rules as product).                                                               |
| `openingMessage`   | Yes      | IVR opening script (non-empty).                                                                                                                         |
| `closingMessage`   | Yes      | IVR closing script (non-empty).                                                                                                                         |
| `purposeType`      | Yes      | Campaign purpose (e.g. `SCHEDULE`, `ONLINE_INTERVIEW`, `INTEREST_CHECK`, `ASSESSMENT_REMINDER`).                                                        |
| `jobDetails`       | Varies   | Object whose fields depend on `purposeType` (job copy, assessment ids, interview slots, meeting URLs, etc.—same as the in-app global campaign creator). |
| `callTime`         | Optional | Human-oriented call time string when using local schedule building.                                                                                     |
| `followUpRetries`  | Optional | Array of `{ "time": "<iso or display string>", "delayDays": 0 }` objects.                                                                               |
| `callStartTimeUtc` | Optional | ISO UTC string for UTC-basis schedules.                                                                                                                 |
| `retryScheduleUtc` | Optional | Optional array for UTC retry slots.                                                                                                                     |
| `callType`         | Optional | `IVR` or `AICall` (case-insensitive accepted).                                                                                                          |
| `aiCallScript`     | Optional | Used when using AI call mode.                                                                                                                           |
| `questions`        | Optional | Optional IVR question list (array).                                                                                                                     |
| `status`           | Optional | Flow status if you need a non-default initial value.                                                                                                    |

For campaign types that use **bulk calling**, include enough schedule data that a first execution time can be computed (typically **`callTime` / `followUpRetries`** and/or **`callStartTimeUtc`** / **`retryScheduleUtc`**, depending on how you configure the campaign—same rules as in the app). **`jobDetails`** is required when the chosen `purposeType` expects it.

### Required fields by `purposeType` (reference)

These mirror the **global / company campaign** creator in the app. Fields marked “bulk schedule” apply to the first call and retries (`callTime` / `followUpRetries` or `callStartTimeUtc` / `retryScheduleUtc`).

| `purposeType`             | Core requirements                                                                                                                                                                                                                                                                         |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`SCHEDULE`**            | **`jobDetails`**: at least `title` (job title). **Interview:** at least **one** entry in **`interviewSlots`** (date + start/end times). Bulk schedule + verified **`phoneNumber`**.                                                                                                       |
| **`ONLINE_INTERVIEW`**    | Same as scheduling, plus **`jobDetails.virtualPlatform`**, **`virtualMeetingUrl`**, and **`virtualAdditionalInstructions`** (all non-empty strings).                                                                                                                                      |
| **`INTEREST_CHECK`**      | **`jobDetails.interestItemDescription`** (non-empty). **No** interview slots required. Bulk schedule + verified **`phoneNumber`**.                                                                                                                                                        |
| **`ASSESSMENT_REMINDER`** | **`jobDetails.assessmentTestId`**. Job label: **`jobTitle`** *or* auto-filled from **`assessmentName`** or campaign **`title`**. No interview slots. Bulk schedule + verified **`phoneNumber`**. Optional SMS templates, `autoAddInvitedCandidates`, `assessmentFirstReminderDelayHours`. |

**Example `jobDetails` fields (assessment-oriented campaigns)**

When `purposeType` is **`ASSESSMENT_REMINDER`**, `jobDetails` typically includes:

| Field                               | Required | Description                                                                                        |
| ----------------------------------- | -------- | -------------------------------------------------------------------------------------------------- |
| `assessmentTestId`                  | Yes      | Quiz / assessment test identifier (string or number).                                              |
| `jobTitle`                          | No\*     | Role label shown in messaging. \*If omitted, the API uses `assessmentName`, then campaign `title`. |
| `assessmentName`                    | No       | Friendly assessment name (also used as a fallback for job title).                                  |
| `assessmentReminderSmsTemplate`     | No       | Max 300 characters.                                                                                |
| `assessmentPostCallSmsTemplate`     | No       | Max 300 characters.                                                                                |
| `autoAddInvitedCandidates`          | No       | Boolean.                                                                                           |
| `assessmentFirstReminderDelayHours` | No       | 0–168 (hours).                                                                                     |

You may also set `assessmentTestId`, SMS templates, `autoAddInvitedCandidates`, and `assessmentFirstReminderDelayHours` at the **top level** of the body (duplicates for convenience); `jobDetails` remains the canonical nested block for that shape.

**Example: UTC-scheduled creation (`ASSESSMENT_REMINDER`)**

```bash theme={null}
curl -s -X POST "https://partner-api.taptalent.io/v1/partner/campaigns" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Q2 eng assessment reminders",
    "timezone": "America/New_York",
    "companySiteId": 12,
    "phoneNumber": "+15551234567",
    "purposeType": "ASSESSMENT_REMINDER",
    "callType": "IVR",
    "openingMessage": "Hi, this is Acme recruiting about your pending technical assessment.",
    "closingMessage": "Thank you. You will receive a text with the link shortly. Goodbye.",
    "callStartTimeUtc": "2025-06-01T15:00:00.000Z",
    "jobDetails": {
      "assessmentTestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "jobTitle": "Software Engineer",
      "assessmentName": "Technical screen",
      "assessmentFirstReminderDelayHours": 48,
      "assessmentReminderSmsTemplate": "Reminder: your assessment link is ready.",
      "autoAddInvitedCandidates": false
    }
  }'
```

**Example: `201` response**

```json theme={null}
{
  "campaign": {
    "id": 5501,
    "title": "Q2 eng assessment reminders",
    "status": "RUNNING",
    "purposeType": "ASSESSMENT_REMINDER",
    "timezone": "America/New_York",
    "companySiteId": 12,
    "callSettings": { "callType": "IVR", "aiCallScript": "" },
    "schedule": {
      "callTimeDisplay": "Jun 01, 2025 11:00 AM",
      "callSchedule": {
        "timeBasis": "UTC",
        "startTimeUtc": "2025-06-01T15:00:00.000Z"
      },
      "followUpRetries": [
        { "time": "16:00", "delayDays": 0 },
        { "time": "17:30", "delayDays": 0 },
        { "time": "19:00", "delayDays": 0 },
        { "time": "20:30", "delayDays": 0 }
      ]
    },
    "messages": {
      "openingMessage": "Hi, this is Acme recruiting about your pending technical assessment.",
      "closingMessage": "Thank you. You will receive a text with the link shortly. Goodbye."
    },
    "assessment": {
      "assessmentTestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "assessmentName": "Technical screen",
      "jobTitle": "Software Engineer",
      "autoAddInvitedCandidates": false,
      "assessmentReminderSmsTemplate": "Reminder: your assessment link is ready.",
      "assessmentPostCallSmsTemplate": "",
      "assessmentFirstReminderDelayHours": 48
    }
  }
}
```

***

## Update campaign

### `PATCH /campaigns/:campaignId`

Updates editable fields for the campaign: title/name, job title, SMS templates, delay, verified phone number, call schedule (until first call starts), opening/closing messages, and call type / AI script—which fields apply depends on the campaign’s **purpose** and current state.

* The campaign must belong to the company and be a **company** campaign (**404** otherwise).

**Common body fields (all optional; only send what changes)**

| Field                                                             | Description                                                                     |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `title` / `campaignName` / `header.title`                         | Rename the campaign (aliases supported).                                        |
| `jobName` / `jobTitle`                                            | Updates the job title on the flow where applicable.                             |
| `assessmentReminderSmsTemplate` / `assessmentPostCallSmsTemplate` | Max 300 chars each.                                                             |
| `assessmentFirstReminderDelayHours`                               | Non-negative number.                                                            |
| `phoneNumber`                                                     | Must exist and be **VERIFIED** on the company.                                  |
| `callTime` / `followUpRetries`                                    | Cannot change after the first outbound call attempt has started (server error). |
| `openingMessage` / `closingMessage`                               | Also accepted as `startMessage` / `endMessage`.                                 |
| `callType` / `aiCallScript`                                       | IVR vs AI call configuration.                                                   |

**Example**

```bash theme={null}
curl -s -X PATCH "https://partner-api.taptalent.io/v1/partner/campaigns/5501" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Q2 eng reminders (updated)",
    "assessmentReminderSmsTemplate": "Acme: finish your tech screen today.",
    "openingMessage": "Hi, calling from Acme about your assessment."
  }'
```

**Example response**

```json theme={null}
{
  "status": "success",
  "flowId": 5501,
  "campaign": {
    "id": 5501,
    "title": "Q2 eng reminders (updated)",
    "status": "RUNNING",
    "purposeType": "ASSESSMENT_REMINDER",
    "timezone": "America/New_York",
    "companySiteId": 12,
    "callSettings": { "callType": "IVR", "aiCallScript": "" },
    "schedule": { "callTimeDisplay": "...", "callSchedule": null, "followUpRetries": [] },
    "messages": {
      "openingMessage": "Hi, calling from Acme about your assessment.",
      "closingMessage": "Thank you. Goodbye."
    },
    "assessment": {
      "assessmentTestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "jobTitle": "Software Engineer",
      "assessmentReminderSmsTemplate": "Acme: finish your tech screen today.",
      "assessmentPostCallSmsTemplate": "",
      "assessmentFirstReminderDelayHours": 48
    }
  }
}
```

The `campaign` object follows the same partner response shape as **Get one campaign**, including `createdAt` / `updatedAt` when present.

***

## Company sites

### `GET /company-sites`

Returns sites the **API key user** may use when creating campaigns. Each entry includes **`companySiteId`** and optional **`site`** metadata (name, address, region).

**Example response**

```json theme={null}
{
  "companySites": [
    {
      "companySiteId": 12,
      "isPrimary": true,
      "site": {
        "id": 12,
        "name": "HQ — New York",
        "address": "1 Example Plaza",
        "city": "New York",
        "state": "NY",
        "country": "US"
      }
    }
  ]
}
```

***

## Leads

### `GET /campaigns/:campaignId/leads`

Lists campaign participants with partner-facing lead fields.

**Query parameters**

| Parameter       | Description                                                                                                  |
| --------------- | ------------------------------------------------------------------------------------------------------------ |
| `search`        | Case-insensitive substring match on first name, last name, or email.                                         |
| `status`        | UI-oriented bucket: `Interested`, `NotInterested`, `NoAnswer`, `Pending` (maps internal execution statuses). |
| `page`, `limit` | Same semantics as list campaigns (`limit` max `100`).                                                        |

**Example**

```bash theme={null}
curl -s -G "https://partner-api.taptalent.io/v1/partner/campaigns/5501/leads" \
  --data-urlencode "search=ada" \
  --data-urlencode "status=Pending" \
  --data-urlencode "page=1" \
  --data-urlencode "limit=50" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example row**

```json theme={null}
{
  "leads": [
    {
      "leadId": 987654,
      "firstName": "Ada",
      "lastName": "Lovelace",
      "email": "ada@example.com",
      "phone": "+15559876543",
      "status": "Pending",
      "flowStatus": "Pending",
      "assessmentSubmitted": "Not submitted",
      "addedAt": "2025-06-02T10:15:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 1,
    "totalPages": 1
  }
}
```

For campaigns that track assessment submission, `assessmentSubmitted` is **`Submitted`** or **`Not submitted`**; `flowStatus` may show **`Completed`** when the lead row completes even if the last dial outcome was pending/no-answer.

***

### `POST /campaigns/:campaignId/leads`

Adds **one or many** leads in a single request. Only the **simple candidate** path is supported (no AI search or marketplace bulk import).

**Body**

| Field   | Type  | Description                                       |
| ------- | ----- | ------------------------------------------------- |
| `leads` | array | Min length 1. Each item must include **`email`**. |

**Each lead**

| Field                   | Required | Description                                          |
| ----------------------- | -------- | ---------------------------------------------------- |
| `email`                 | Yes      | Valid email; primary dedupe key with internal rules. |
| `firstName`, `lastName` | No       | Empty string allowed.                                |
| `phoneNumber`           | No       | E.164 or local format per company rules.             |
| `linkedin`              | No       | Profile URL or handle string.                        |

**Idempotency:** the API does **not** guarantee idempotent adds. Re-posting the same email may return **`skipped`** counts if the candidate is already on the flow or blocked by duplicate/contact rules. Treat **`added`** / **`skipped`** as best-effort telemetry and reconcile with **`GET .../leads`** if needed.

**Example**

```bash theme={null}
curl -s -X POST "https://partner-api.taptalent.io/v1/partner/campaigns/5501/leads" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "leads": [
      {
        "email": "ada@example.com",
        "firstName": "Ada",
        "lastName": "Lovelace",
        "phoneNumber": "+15559876543",
        "linkedin": "https://www.linkedin.com/in/example"
      },
      {
        "email": "grace@example.com",
        "firstName": "Grace",
        "lastName": "Hopper"
      }
    ]
  }'
```

**Example response**

```json theme={null}
{
  "added": 2,
  "skipped": 0
}
```

`skipped` increments when a row is not added because of duplicates, prior contact, or internal eligibility checks.

***

### `DELETE /campaigns/:campaignId/leads/:leadId`

Removes a lead from the campaign. **`leadId`** is the **`leadId`** returned by the list endpoint (internal `talentId`).

```bash theme={null}
curl -s -X DELETE \
  "https://partner-api.taptalent.io/v1/partner/campaigns/5501/leads/987654" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example response**

```json theme={null}
{
  "status": "success",
  "leadId": 987654
}
```

***

## Webhooks (campaign completion)

When a lead reaches a terminal campaign outcome, TapTalent sends a webhook event:

* **Event:** `candidate.flow_completed`
* **When sent:** after a lead reaches a terminal status such as completed or terminated
* **Scope:** company-level webhook URL configured in your Partner API settings

**Example payload**

```json theme={null}
{
  "event": "candidate.flow_completed",
  "timestamp": "2026-04-20T10:15:30.000Z",
  "companyId": "12345",
  "data": {
    "flowId": 5501,
    "talentId": 987654,
    "flowStatus": "COMPLETED",
    "source": "campaign_execution",
    "reason": null,
    "completedAt": "2026-04-20T10:15:30.000Z",
    "flow": {
      "name": "Q2 eng assessment reminders",
      "purposeType": "ASSESSMENT_REMINDER"
    },
    "candidate": {
      "firstName": "Ada",
      "lastName": "Lovelace",
      "email": "ada@example.com",
      "phoneNumber": "+15559876543"
    }
  }
}
```

See [Webhooks overview](/webhooks/overview) and [Webhook event types](/webhooks/events) for delivery headers, verification, retries, and handler best practices.

***

## Errors

Partner routes normalize failures to JSON:

```json theme={null}
{
  "error": {
    "code": "INVALID_REQUEST",
    "type": "validation_error",
    "message": "One or more fields are invalid.",
    "details": {
      "jobDetails.jobTitle": "\"jobTitle\" is required"
    }
  }
}
```

| HTTP | `code`                            | When                                                                                              |
| ---- | --------------------------------- | ------------------------------------------------------------------------------------------------- |
| 400  | `INVALID_REQUEST`                 | Invalid body fields; see `message` and per-field `details`.                                       |
| 400  | `PURPOSE_NOT_ALLOWED_FOR_PARTNER` | The operation is not allowed for this campaign’s purpose or current configuration; see `message`. |
| 404  | `NOT_FOUND`                       | Campaign missing, wrong company, or not a company campaign.                                       |
| 403  | `FORBIDDEN`                       | Client profile / company mismatch for the key (rare on create).                                   |
| 500  | `INTERNAL_ERROR`                  | Unexpected server-side failure. The response uses a **generic message** only.                     |

### Debugging failed requests

If you see a generic error or **`500`** in your API client:

1. For **`INVALID_REQUEST`**, read **`error.message`** (and **`details`** when present). For **`INTERNAL_ERROR`**, review your request payload and retry; if the issue persists, contact your workspace owner/support with the request context.
2. Check the **terminal where the Partner API (Node) is running**. Failed creates are logged as **`[POST /v1/partner/campaigns]`** with the underlying exception (operators only).
3. Confirm **`companySiteId`** appears in **`GET /v1/partner/company-sites`** for the same API key user, and that **`phoneNumber`** is a **verified** company number (same rules as the in-app global campaign creator).
4. Confirm your API key’s user has access to the assessment **`assessmentTestId`** you pass in **`jobDetails`**.

***

## Operational notes

* **Rate limiting:** follows the same limits as other Partner API routes for your workspace.
* **Voice (IVR):** creating or updating IVR messages may trigger text-to-speech generation in the background, as in the product. Administrative or system-only operations are not exposed on the Partner API.
