# CRM · Marketing

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## POST /crm/marketing/campaign-manager/list

**List campaigns (Campaign Manager)**

`operationId: MarketingController_managerList`

Totals across **every** campaign in the org (`summary`: counts per status, budget, spent and the other metrics, tracked/untracked counts, `statusOptions`) plus one page of enriched campaign cards. Each card carries its computed `status`/`statusLabel`, `channel` (ads | email | offline), `metrics` gathered from every source that reports on it (ad platform insights, linked social posts, the linked email broadcast, checkout attribution, manually entered actuals), `tracking` (which sources, or "Not tracked"), `failure` for a failed launch, `adsReadiness`, `blockedActions` and `testLaunch`. `status` and `search` (name, description, type, platforms) filter the page only; totals never change with them.

#### Signature

```http
POST /crm/marketing/campaign-manager/list (body) -> { summary, data: Campaign[], total, page, pageSize, hasNext }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

```json
{
  "status": "active",
  "pageSize": 24
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | { summary, data: Campaign[], total, page, pageSize, hasNext } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/campaign-manager/social-feeds

**Social feeds by platform**

`operationId: MarketingController_managerSocialFeeds`

One column per social network: its connection `state` (connected | needs_reconnect | not_connected | no_account), a `message` and next `action` when it needs attention, and the latest synced posts.

#### Signature

```http
GET /crm/marketing/campaign-manager/social-feeds (limit?: integer) -> Platform feeds
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `limit` | query | integer | — | Posts per platform, 1–50 (default 10). |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Platform feeds |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/create

**Create a campaign**

`operationId: MarketingController_managerCreate`

Creates a campaign in `draft`. `name` and `type` are required; `type` is one of `email`, `social`, `ppc`, `display`, `video`, `radio`, `billboard`, `newspaper`, `magazine`, `tv`, `website`. Editable fields: name, description, type, objective, budget (≥ 0), budgetType (daily | lifetime), dailyBudget, platforms, landingPage, utmParameters, tags, notes, location, timeSlots, frequency, duration, audienceIds, targetAudience, audiences, timezone, broadcastId (email campaigns), creatives and the Smart Builder state. The ad account is never set here — see `POST …/{id}/ad-account`.

#### Signature

```http
POST /crm/marketing/campaign-manager/create (body) -> The new campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NAME_REQUIRED | A campaign needs a name | `name` missing or blank (create, or an update that sends it). | — |
| `409` | DUPLICATE_CREATE | This campaign was just created — open it from Campaigns to keep editing it | The same create was sent twice in a row (double click / resend). | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

```json
{
  "name": "Fall sale — Meta",
  "type": "social",
  "platforms": [
    "facebook",
    "instagram"
  ],
  "budget": 500,
  "budgetType": "lifetime"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The new campaign card |
| `400` | A campaign needs a name — `name` missing or blank (create, or an update that sends it). |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `409` | This campaign was just created — open it from Campaigns to keep editing it — The same create was sent twice in a row (double click / resend). |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/social-draft

**Draft a social post for a campaign**

`operationId: MarketingController_managerSocialDraft`

Creates a draft social post (a `message` with status `draft`, never sent) for one platform — `facebook`, `instagram`, `linkedin`, `twitter`, `tiktok`, `pinterest` — linked to the campaign when `campaignId` is given. It is reviewed and published from Social › Posts.

#### Signature

```http
POST /crm/marketing/campaign-manager/social-draft (body) -> { id, platform, status: "draft", text, campaignId, openIn }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | BAD_PLATFORM | Platform must be one of: facebook, instagram, linkedin, twitter, tiktok, pinterest | Unknown platform. | — |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

```json
{
  "campaignId": "66f1a2b3c4d5e6f708192a3b",
  "platform": "instagram",
  "text": "Fall sale starts Friday 🍂"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | { id, platform, status: "draft", text, campaignId, openIn } |
| `400` | Platform must be one of: facebook, instagram, linkedin, twitter, tiktok, pinterest — Unknown platform. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/campaign-manager/ads-readiness

**Check whether each ad platform can publish ads**

`operationId: MarketingController_managerAdsReadiness`

For every ad platform (`facebook`, `instagram`, `google`, `linkedin`, `tiktok`, `pinterest`, `twitter`), or for one campaign when `campaign` is given, the answer is **ready** or the list of issues in the way. Each issue has a `fix: { label, target }`, where `target` is either an in-app screen (`{ app: "/social-manager", tab: "accounts", section }`, the platform's "Ad account" settings) or a page on the platform itself (`{ url }`).

What is checked, read-only on the platforms (nothing is created and nothing is spent):
- the connection exists, is turned on, and has a token that has not expired;
- the token carries the ads permission (Meta `ads_management` + `ads_read` from `/me/permissions`; Google `adwords` scope; LinkedIn `rw_ads`; Pinterest `ads:read`). TikTok needs a TikTok for Business (Marketing API) advertiser authorization, and X needs X Ads API access; posting-only connections are reported as `ads_api_unavailable`;
- an ad account is chosen: the campaign's own override, otherwise the connection default (`adAccountIds` on the connection);
- that account is one the login can use and is fully ready: active, with a payment method where the API reports it (Meta `account_status`, `disable_reason`, `funding_source_details`; Google customer status and an approved billing setup);
- the identity ads run as: a Facebook Page for Facebook, an Instagram professional account linked to a Page for Instagram.

Results are cached per organization for about a minute; `refresh=1` checks again.

#### Signature

```http
GET /crm/marketing/campaign-manager/ads-readiness (campaign?: string, refresh?: string) -> `{ checkedAt, campaign, applies, ready, blockedReason, summary: { total, ready, attention }, platforms: [{ platform, label, connection, ready, status, adAccount, issues: [{ code, message, severity, fix }], supportsTestLaunch, guide }] }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Issue codes: `not_connected`, `connection_inactive`, `token_expired`, `token_invalid`, `missing_ads_permission`, `ads_api_unavailable`, `no_ad_account_selected`, `no_usable_ad_account`, `ad_account_not_found`, `ad_account_not_ready`, `no_page`, `no_instagram_account`, `platform_integration_outdated`, `check_failed`.
- Also mounted as `GET /crm/marketing/campaign-manager/ads-readiness/{campaignId}`.
- Campaign cards from `POST /crm/marketing/campaign-manager/list` carry the same result as `adsReadiness`, plus `blockedActions` (`launch`, `retry`, `schedule` → the reason) while a targeted platform is not ready.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/campaign-manager/ad-accounts`
- `POST /crm/marketing/campaign-manager/{id}/launch`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `campaign` | query | string | — | Narrow to this campaign: only its ad platforms, with its own ad account overrides applied. |
| `refresh` | query | string | — | `1` to skip the short cache. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ checkedAt, campaign, applies, ready, blockedReason, summary: { total, ready, attention }, platforms: [{ platform, label, connection, ready, status, adAccount, issues: [{ code, message, severity, fix }], supportsTestLaunch, guide }] }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

Example response:

```json
{
  "ready": false,
  "blockedReason": "Facebook, Instagram — No ad account is chosen for Facebook & Instagram (Meta).",
  "summary": {
    "total": 2,
    "ready": 0,
    "attention": 2
  },
  "platforms": [
    {
      "platform": "facebook",
      "label": "Facebook",
      "connection": "meta",
      "ready": false,
      "status": "needs_attention",
      "adAccount": null,
      "issues": [
        {
          "code": "no_ad_account_selected",
          "severity": "blocker",
          "message": "No ad account is chosen for Facebook & Instagram (Meta).",
          "fix": {
            "label": "Choose ad account",
            "target": {
              "app": "/social-manager",
              "tab": "accounts",
              "section": "meta"
            }
          }
        }
      ]
    }
  ]
}
```

## GET /crm/marketing/campaign-manager/ads-readiness/{campaignId}

**Ads readiness for one campaign**

`operationId: MarketingController_managerAdsReadinessFor`

Same as `GET …/ads-readiness?campaign=` — only the platforms this campaign targets.

#### Signature

```http
GET /crm/marketing/campaign-manager/ads-readiness/{campaignId} (campaignId: string, refresh?: string) -> Readiness per platform
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/campaign-manager/ads-readiness`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `campaignId` | path | string | yes | Campaign id. |
| `refresh` | query | "1" \| "true" | — | Re-check the platforms instead of the short cache. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Readiness per platform |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/campaign-manager/ad-accounts

**List the ad accounts a platform connection can use**

`operationId: MarketingController_managerAdAccounts`

The ad accounts the connected login can see on a platform, each marked `selectable` or not with `reasons` in plain words (inactive or disabled, no payment method, limited API access…) and a `fixUrl` on the platform where there is one. `selected` is the connection's current default. When nothing is usable, `guide` holds the platform's own steps to create one (official link included). `manualEntry` is true where an id can be typed in and verified (Meta). Without `platform`, every connection is returned as `{ connections: [...] }`.

#### Signature

```http
GET /crm/marketing/campaign-manager/ad-accounts (platform?: string, refresh?: string) -> `{ connection, label, platforms, connected, issues, canList, accounts: [{ id, name, currency, status, selectable, reasons, paymentMethod, business, fixUrl }], usableCount, selected, manualEntry, guide, checkedAt }`
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Also mounted as `GET /crm/marketing/campaign-manager/ad-accounts/{platform}`.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/marketing/campaign-manager/ad-accounts/default`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `platform` | query | string | — | A platform (`facebook`, `instagram`, `google`, `linkedin`, `tiktok`, `pinterest`, `twitter`) or connection key (`meta`). Facebook and Instagram share the Meta connection. |
| `refresh` | query | string | — | `1` to skip the short cache. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | `{ connection, label, platforms, connected, issues, canList, accounts: [{ id, name, currency, status, selectable, reasons, paymentMethod, business, fixUrl }], usableCount, selected, manualEntry, guide, checkedAt }` |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/campaign-manager/ad-accounts/{platform}

**Ad accounts for one platform**

`operationId: MarketingController_managerAdAccountsFor`

Same as `GET …/ad-accounts?platform=`.

#### Signature

```http
GET /crm/marketing/campaign-manager/ad-accounts/{platform} (platform: string, refresh?: string) -> Ad accounts, each marked usable or not with the reason
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/campaign-manager/ad-accounts`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `platform` | path | string | yes | Ad platform key. |
| `refresh` | query | "1" \| "true" | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Ad accounts, each marked usable or not with the reason |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/ad-accounts/default

**Choose a platform connection's ad account**

`operationId: MarketingController_managerSetDefaultAdAccount`

Saves the ad account that campaigns on this platform launch from (stored on the connection as `adAccountIds`; also `adsAccountId` for Google and `advertiserId` for TikTok, which those integrations read). The account is checked against the platform again first: only one the login can use **and** that is fully ready is accepted. `accountId: null` clears it.

#### Signature

```http
POST /crm/marketing/campaign-manager/ad-accounts/default (body) -> The connection's ad accounts, as `GET …/ad-accounts`, with the new `selected`.
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- The error body carries `reason` (the code above) next to the message.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `409` | ad_account_not_found | … the connected login has no access to ad account … | The id is not one this login can use. | Pick one from the list, or give the login access on the platform and refresh. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/campaign-manager/ad-accounts`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

```json
{
  "platform": "facebook",
  "accountId": "act_24661883323447662"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The connection's ad accounts, as `GET …/ad-accounts`, with the new `selected`. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `409` | … the connected login has no access to ad account … — The id is not one this login can use. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/campaign-manager/{id}

**Get a campaign card**

`operationId: MarketingController_managerGet`

#### Signature

```http
GET /crm/marketing/campaign-manager/{id} (id: string) -> The enriched campaign card (same shape as the list rows)
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The enriched campaign card (same shape as the list rows) |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /crm/marketing/campaign-manager/{id}

**Delete a campaign**

`operationId: MarketingController_managerDelete`

Cancels its schedules, pauses it on its ad platforms if it is live (the platform campaign and its history are kept in the ad account), removes any paused test campaign, then deletes the record.

#### Signature

```http
DELETE /crm/marketing/campaign-manager/{id} (id: string) -> { deleted: true, id }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { deleted: true, id } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/update

**Update a campaign**

`operationId: MarketingController_managerUpdate`

Changes any editable field (see create). On a live ad campaign a new name or budget is also pushed to its platforms; a platform refusal is logged and the local change is kept.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/update (id: string, body) -> The campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NAME_REQUIRED | A campaign needs a name | `name` missing or blank (create, or an update that sends it). | — |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

```json
{
  "budget": 750
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card |
| `400` | A campaign needs a name — `name` missing or blank (create, or an update that sends it). |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/launch

**Launch a campaign**

`operationId: MarketingController_managerLaunch`

Launches through the campaign's channel. An **ad** campaign is refused up front while any platform it targets is not ready: nothing is sent to any platform and the campaign keeps its status. `POST …/{id}/schedule` applies the same check before booking a launch, and a scheduled launch that comes due while a platform is not ready is marked failed with the reason.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/launch (id: string) -> The campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `409` | ads_not_ready | This campaign can't launch yet. Facebook, Instagram — No ad account is chosen for Facebook & Instagram (Meta). | A targeted ad platform is not connected, lacks the ads permission, or has no usable ad account. | The body carries `readiness` (as `GET …/ads-readiness`) — follow each issue's `fix`, then launch again. |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/campaign-manager/ads-readiness`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `409` | This campaign can't launch yet. Facebook, Instagram — No ad account is chosen for Facebook & Instagram (Meta). — A targeted ad platform is not connected, lacks the ads permission, or has no usable ad account. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/pause

**Pause a campaign**

`operationId: MarketingController_managerPause`

Only an active campaign. Pauses it on every ad platform it is live on (results kept in `platformResults`) and, for an email campaign, cancels the linked broadcast if it has not gone out.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/pause (id: string) -> The campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NOT_ACTIVE | Only an active campaign can be paused (this one is <status>) | The campaign is not active. | — |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card |
| `400` | Only an active campaign can be paused (this one is <status>) — The campaign is not active. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/resume

**Resume a paused campaign**

`operationId: MarketingController_managerResume`

Only a paused campaign. Re-activates it on its ad platforms and sets it active.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/resume (id: string) -> The campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NOT_PAUSED | Only a paused campaign can be resumed (this one is <status>) | The campaign is not paused. | — |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card |
| `400` | Only a paused campaign can be resumed (this one is <status>) — The campaign is not paused. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/complete

**End a campaign**

`operationId: MarketingController_managerComplete`

Only an active or paused campaign. Pauses it on its ad platforms (if active), cancels any pending schedule and marks it completed.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/complete (id: string) -> The campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NOT_RUNNING | Only a running or paused campaign can be ended | Any other status. | — |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card |
| `400` | Only a running or paused campaign can be ended — Any other status. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/schedule

**Schedule a launch**

`operationId: MarketingController_managerSchedule`

Books a `schedule` record owned by the campaign that launches it at `startDate` (and ends it at `endDate` when given), replacing any earlier booking, and sets the campaign `scheduled`. Only draft, scheduled or failed campaigns. An ad campaign is only booked when every platform it targets is ready (same check as launch).

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/schedule (id: string, body) -> The campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NOT_SCHEDULABLE | A <status> campaign cannot be scheduled | Status is not draft, scheduled or failed. | — |
| `409` | ads_not_ready | This campaign can't be scheduled yet. … | A targeted ad platform is not ready; body carries `readiness`. | — |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

```json
{
  "startDate": "2026-10-15T14:00:00Z",
  "endDate": "2026-10-31T23:59:00Z"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card |
| `400` | A <status> campaign cannot be scheduled — Status is not draft, scheduled or failed. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `409` | This campaign can't be scheduled yet. … — A targeted ad platform is not ready; body carries `readiness`. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/unschedule

**Cancel a scheduled launch**

`operationId: MarketingController_managerUnschedule`

Deletes the pending schedule (which cancels its queued jobs) and returns the campaign to draft.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/unschedule (id: string) -> The campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NOT_SCHEDULED | This campaign is not scheduled | Status is not scheduled. | — |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card |
| `400` | This campaign is not scheduled — Status is not scheduled. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/duplicate

**Duplicate a campaign**

`operationId: MarketingController_managerDuplicate`

A new draft with the same settings and none of the runtime state (platform ids, results, metrics, schedule). `name` defaults to "<name> (Copy)".

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/duplicate (id: string, body) -> The new campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |
| `409` | DUPLICATE_CREATE | This campaign was just created — open it from Campaigns to keep editing it | The same duplicate was sent twice in a row. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

```json
{
  "name": "Fall sale — TikTok"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The new campaign card |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `409` | This campaign was just created — open it from Campaigns to keep editing it — The same duplicate was sent twice in a row. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/actuals

**Enter actual results**

`operationId: MarketingController_managerActuals`

For channels no system reports on (radio, print, TV…): `spent`, `reach`, `impressions`, `clicks`, `conversions`, `revenue` (each ≥ 0) and an optional `note`. They replace the previous entry and count toward the card's metrics.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/actuals (id: string, body) -> The campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | BAD_FIGURE | <field> must be a number of zero or more | A figure is negative or not a number. | — |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

```json
{
  "spent": 1200,
  "reach": 40000,
  "note": "Invoice from KXYZ radio"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card |
| `400` | <field> must be a number of zero or more — A figure is negative or not a number. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/refresh-metrics

**Pull platform insights**

`operationId: MarketingController_managerRefresh`

Fetches spend, reach, impressions, clicks and conversions from every ad platform the campaign is live on and stores them (`platformMetrics`). Platforms that fail are listed in `platformMetrics.errors`.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/refresh-metrics (id: string) -> The campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NOT_LIVE | This campaign is not live on any ad platform yet, so there are no platform insights to pull | No platform reference on the campaign. | — |
| `502` | NO_INSIGHTS | No platform returned insights: <platform>: <error> | Every platform failed. | — |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card |
| `400` | This campaign is not live on any ad platform yet, so there are no platform insights to pull — No platform reference on the campaign. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |
| `502` | No platform returned insights: <platform>: <error> — Every platform failed. |

## POST /crm/marketing/campaign-manager/{id}/copy

**Save ad copy**

`operationId: MarketingController_managerCopy`

Saves headlines, descriptions and a call to action for one `platform` (default `all`) as the campaign's creative for that platform, replacing the previous one. Nothing is published.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/copy (id: string, body) -> The campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NO_COPY | Send at least one headline or description | Both lists empty. | — |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

```json
{
  "platform": "facebook",
  "headlines": [
    "30% off this week"
  ],
  "descriptions": [
    "Ends Sunday."
  ],
  "cta": "SHOP_NOW"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card |
| `400` | Send at least one headline or description — Both lists empty. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/ad-account

**Override a campaign's ad account on one platform**

`operationId: MarketingController_managerSetAdAccount`

Launch this campaign from a different ad account than the connection default, on one platform (Facebook and Instagram share one Meta account). Verified exactly like the default. `accountId: null` returns to the default. Only while the campaign is a draft, scheduled or failed.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/ad-account (id: string, body) -> The campaign card, with `adAccounts` and fresh `adsReadiness`.
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card, with `adAccounts` and fresh `adsReadiness`. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/test-launch

**Test-launch a campaign on Meta, paused**

`operationId: MarketingController_managerTestLaunch`

Proves the Meta setup end to end without spending: creates the campaign on the chosen Meta ad account in **PAUSED** status, with no budget, no ad sets and no ads, reads it back, and deletes it at once if Meta reports anything other than PAUSED. The platform campaign id is kept on the campaign (`testLaunch.launches`) with an Ads Manager link. Only Meta supports this; other platforms are never test-created.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/test-launch (id: string) -> The campaign card with `testLaunch.launches`.
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `409` | ads_not_ready | Test launch needs Meta to be ready first. … | Meta is not ready (no ad account chosen, missing permission…). | Follow the `readiness` fixes. |
| `400` | test_launch_unsupported | A paused test launch is available for Facebook and Instagram … | The campaign targets neither Facebook nor Instagram. | — |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/marketing/campaign-manager/{id}/test-remove`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card with `testLaunch.launches`. |
| `400` | A paused test launch is available for Facebook and Instagram … — The campaign targets neither Facebook nor Instagram. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `409` | Test launch needs Meta to be ready first. … — Meta is not ready (no ad account chosen, missing permission…). |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaign-manager/{id}/test-remove

**Remove a paused test campaign**

`operationId: MarketingController_managerRemoveTest`

Deletes the paused test campaign on Meta and clears it from the campaign. Deleting the campaign record removes its test too.

#### Signature

```http
POST /crm/marketing/campaign-manager/{id}/test-remove (id: string) -> The campaign card
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `502` | test_remove_failed | The test could not be removed on Meta: … | Meta refused the delete. | Retry, or delete it in Ads Manager. |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The campaign card |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |
| `502` | The test could not be removed on Meta: … — Meta refused the delete. |

## GET /crm/marketing/campaign-manager-performance

**Campaign performance summary**

`operationId: MarketingController_managerPerformance`

What the marketing AI answers "how are my campaigns doing?" from: the org totals, the top 5 campaigns by conversions, failed campaigns with their reason, untracked campaigns, and a compact row per campaign (first 100).

#### Signature

```http
GET /crm/marketing/campaign-manager-performance () -> { summary, topByConversions, failed, untracked, campaigns }
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | { summary, topByConversions, failed, untracked, campaigns } |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/social-profiles/{platform}

**Get connected social profiles**

`operationId: MarketingController_getSocialProviders`

The social accounts available to publish marketing campaigns from. Supply `platform` to narrow it.

#### Signature

```http
GET /crm/marketing/social-profiles/{platform} (platform: string) -> Connected profiles
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/platforms`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `platform` | path | string | yes | One of `facebook`, `instagram`, `tiktok`, `twitter`, `linkedin`, `pinterest`. Omit for all. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Connected profiles |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/campaigns

**List marketing campaigns**

`operationId: MarketingController_getCampaigns`

The org's marketing campaigns, with their status and schedule.

#### Signature

```http
GET /crm/marketing/campaigns (status?: string, type?: string, platforms?: string, tags?: string, createdBy?: string, search?: string, budgetMin?: integer, budgetMax?: integer, startDate?: string, endDate?: string, page?: string, limit?: string, sortField?: string, sortDirection?: string) -> Marketing campaigns
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/campaigns/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `status` | query | string | — | Filter by status. |
| `type` | query | string | — | Filter by type. |
| `platforms` | query | string | — | Comma-separated platforms. |
| `tags` | query | string | — | Comma-separated tags. |
| `createdBy` | query | string | — | Creator email. |
| `search` | query | string | — | Search text. |
| `budgetMin` | query | integer | — | Minimum budget. |
| `budgetMax` | query | integer | — | Maximum budget. |
| `startDate` | query | string | — | ISO date — start of the range. |
| `endDate` | query | string | — | ISO date — end of the range. |
| `sortDirection` | query | "asc" \| "desc" | — |  |
| `page` | query | string | — | Page number (1-based). |
| `limit` | query | string | — | Maximum rows. |
| `sortField` | query | string | — | Field to sort by. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Marketing campaigns |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaigns

**Create a marketing campaign**

`operationId: MarketingController_createCampaign`

Creates a marketing campaign. Distinct from `/crm/ads` — that manages paid advertising on ad platforms; this covers owned marketing across channels.

#### Signature

```http
POST /crm/marketing/campaigns (body) -> The created campaign
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/campaign-types`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The campaign to create.

```json
{
  "name": "Summer newsletter",
  "type": "email",
  "audienceId": "AUD-4821"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created campaign |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/campaigns/{id}

**Get a marketing campaign**

`operationId: MarketingController_getCampaign`

Fetches one campaign with its targeting, schedule and current state.

#### Signature

```http
GET /crm/marketing/campaigns/{id} (id: string) -> The campaign
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/campaigns/{id}/analytics`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The campaign |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## PUT /crm/marketing/campaigns/{id}

**Update a marketing campaign**

`operationId: MarketingController_updateCampaign`

Updates a campaign. Changes to one already sent affect only future sends — a delivered email cannot be edited.

#### Signature

```http
PUT /crm/marketing/campaigns/{id} (id: string, body) -> The updated campaign
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `DELETE /crm/marketing/campaigns/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

Fields to change.

```json
{
  "name": "Summer newsletter (v2)"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated campaign |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /crm/marketing/campaigns/{id}

**Delete a marketing campaign**

`operationId: MarketingController_deleteCampaign`

Deletes a campaign. Its historical performance data goes with it — export analytics first if you need them.

#### Signature

```http
DELETE /crm/marketing/campaigns/{id} (id: string) -> Deletion result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/campaigns/{id}/analytics`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Deletion result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/campaigns/{id}/analytics

**Get campaign analytics**

`operationId: MarketingController_getCampaignAnalytics`

Performance for one campaign — sends, opens, clicks and conversions.

#### Signature

```http
GET /crm/marketing/campaigns/{id}/analytics (id: string, startDate?: string, endDate?: string) -> Campaign analytics
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Campaign not found | No campaign has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/marketing/campaigns/aggregated-metrics`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |
| `startDate` | query | string | — | ISO date — start of the range. |
| `endDate` | query | string | — | ISO date — end of the range. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Campaign analytics |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Campaign not found — No campaign has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/marketing/campaigns/aggregated-metrics

**Get aggregated campaign metrics**

`operationId: MarketingController_getAggregatedMetrics`

Combines metrics across several campaigns in one call — the read behind a marketing overview, rather than fetching analytics per campaign.

#### Signature

```http
POST /crm/marketing/campaigns/aggregated-metrics (body) -> Aggregated metrics
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/dashboard`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

Which campaigns and period to aggregate.

```json
{
  "campaignIds": [
    "CMP-4821",
    "CMP-4822"
  ],
  "startDate": "2026-08-01",
  "endDate": "2026-08-31"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Aggregated metrics |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/platforms

**List marketing platforms**

`operationId: MarketingController_getAvailablePlatforms`

The channels marketing campaigns can run on, and which are connected for this org.

#### Signature

```http
GET /crm/marketing/platforms () -> Marketing platforms
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/campaign-types`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgId` | header | string | yes |  |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Marketing platforms |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/campaign-types

**List campaign types**

`operationId: MarketingController_getCampaignTypes`

The kinds of campaign that can be created, with the fields each requires. Read this to build a campaign form generically.

#### Signature

```http
GET /crm/marketing/campaign-types () -> Campaign types
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/marketing/campaigns`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Campaign types |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/dashboard

**Get the marketing dashboard**

`operationId: MarketingController_getCampaignDashboard`

Headline marketing figures across campaigns and channels.

#### Signature

```http
GET /crm/marketing/dashboard (startDate?: string, endDate?: string) -> Dashboard figures
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/health`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `startDate` | query | string | — | ISO date — start of the range. |
| `endDate` | query | string | — | ISO date — end of the range. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Dashboard figures |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/marketing/health

**Get marketing health**

`operationId: MarketingController_healthCheck`

Whether the marketing integrations are connected and working — check this first when campaigns fail to send.

#### Signature

```http
GET /crm/marketing/health () -> Integration health
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/marketing/platforms`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Integration health |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

