# Campaigns · Management API

> Management API reference for campaigns: GET /sites/{id}/campaigns, POST /sites/{id}/campaigns, PATCH /sites/{id}/campaigns/{campaignId}.

> Markdown version of https://kanorio.com/en/developers/management-api/campaigns. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp

Base URL: `https://app.kanorio.com/api/v1` · [Errors](https://kanorio.com/en/developers/basics/errors) · [Pagination](https://kanorio.com/en/developers/basics/pagination)

### List campaigns

`GET /sites/{id}/campaigns`

- Stability: beta
- Permission: `marketing:view`
- Rate limit: default (120/minute per token)
- MCP tool: `campaigns_list`

List email campaigns (newest updated first) with status, audience segment and delivery stats for sent ones. Errors: FORBIDDEN, PLAN_REQUIRED, INVALID_CURSOR.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `limit` | query | integer | no | Maximum number of items to return, 1–100. Defaults to 20. |
| `cursor` | query | string | no | Opaque cursor from the previous page's nextCursor. Omit for the first page. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].id` | string | yes | — |
| `data[].name` | string | yes | — |
| `data[].channel` | string | yes | — |
| `data[].status` | string | yes | DRAFT, SCHEDULED, SENDING, PAUSED, SENT or CANCELED. Agents can only edit DRAFT. |
| `data[].pausedReason` | string \| null | yes | — |
| `data[].segmentName` | string \| null | yes | — |
| `data[].scheduledAt` | string \| null | yes | — |
| `data[].startedAt` | string \| null | yes | — |
| `data[].completedAt` | string \| null | yes | — |
| `data[].updatedAt` | string | yes | — |
| `data[].recipientCount` | integer \| null | yes | — |
| `data[].stats` | object \| null | yes | — |
| `data[].stats.recipients` | integer | yes | — |
| `data[].stats.pending` | integer | yes | — |
| `data[].stats.sent` | integer | yes | — |
| `data[].stats.delivered` | integer | yes | — |
| `data[].stats.bounced` | integer | yes | — |
| `data[].stats.complained` | integer | yes | — |
| `data[].stats.notSent` | integer | yes | — |
| `data[].stats.opened` | integer | yes | — |
| `data[].stats.clicked` | integer | yes | — |
| `data[].stats.unsubscribed` | integer | yes | — |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `PLAN_REQUIRED` | 403 | — |
| `INVALID_CURSOR` | 400 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/campaigns" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "data": [
    {
      "id": "camp_example01",
      "name": "October seasonal bake",
      "channel": "EMAIL",
      "status": "DRAFT",
      "pausedReason": null,
      "segmentName": "Ordered in the last 90 days",
      "scheduledAt": null,
      "startedAt": null,
      "completedAt": null,
      "updatedAt": "2026-10-06T08:00:00.000Z",
      "recipientCount": 320,
      "stats": null
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

### Create draft

`POST /sites/{id}/campaigns`

- Stability: beta
- Permission: `marketing:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `campaigns_create_draft`

Create an email campaign as a DRAFT. Agents cannot send or schedule campaigns; the owner reviews and sends from the console. content is optional; when provided it must match the email design-template content shape (subject, previewText, blocks). Audience defaults to the all-subscribers segment. Reference images as asset:<assetId> (media.upload, media.import_stock). Errors: FORBIDDEN, PLAN_REQUIRED, VALIDATION, INVALID_CONTENT (details.detail lists issues), CHANNEL_UNAVAILABLE, ASSET_NOT_FOUND, ASSET_NOT_ALLOWED.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | — |
| `channel` | "EMAIL" | no | Defaults to EMAIL. |
| `content` | object | no | — |

**Response** (201)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `name` | string | yes | — |
| `channel` | string | yes | — |
| `status` | string | yes | — |
| `content` | object | yes | Channel content (email: subject, previewText, blocks...). Media-library images appear as asset:<assetId>. |
| `assets` | object | yes | The media-library images the content uses, by assetId. |
| `audience` | object \| null | yes | — |
| `audience.kind` | "segment" | yes | — |
| `audience.segmentId` | string | yes | — |
| `scheduledAt` | string \| null | yes | — |
| `recipientCount` | integer \| null | yes | — |
| `updatedAt` | string | yes | — |
| `timeZone` | string | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `PLAN_REQUIRED` | 403 | — |
| `VALIDATION` | 400 | — |
| `INVALID_CONTENT` | 400 | details.detail lists issues |
| `CHANNEL_UNAVAILABLE` | 422 | — |
| `ASSET_NOT_FOUND` | 404 | — |
| `ASSET_NOT_ALLOWED` | 422 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/campaigns" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"October seasonal bake","channel":"EMAIL"}'
```

```json
{
  "id": "camp_example01",
  "name": "October seasonal bake",
  "channel": "EMAIL",
  "status": "DRAFT",
  "content": {
    "subject": "October special: chestnut croissants are here",
    "body": "This month we're baking..."
  },
  "assets": {},
  "audience": {
    "kind": "segment",
    "segmentId": "seg_example01"
  },
  "scheduledAt": null,
  "recipientCount": 320,
  "updatedAt": "2026-10-06T08:00:00.000Z",
  "timeZone": "Asia/Taipei"
}
```

### Update draft

`PATCH /sites/{id}/campaigns/{campaignId}`

- Stability: beta
- Permission: `marketing:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `campaigns_update_draft`

Update a DRAFT campaign's name, content and/or audience segment. Only drafts can be changed by agents (NOT_EDITABLE otherwise); scheduling and sending stay in the console. Errors: FORBIDDEN, PLAN_REQUIRED, CAMPAIGN_NOT_FOUND, NOT_EDITABLE, INVALID_CONTENT, INVALID_AUDIENCE, SEGMENT_NOT_FOUND, ASSET_NOT_FOUND, ASSET_NOT_ALLOWED.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `campaignId` | path | string | yes | Campaign id. Get it from campaigns.list. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no | — |
| `content` | object | no | — |
| `audience` | object | no | Who receives it: a segment id from segments.list. New drafts default to the all-subscribers segment. |
| `audience.kind` | "segment" | yes | — |
| `audience.segmentId` | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `name` | string | yes | — |
| `channel` | string | yes | — |
| `status` | string | yes | — |
| `content` | object | yes | Channel content (email: subject, previewText, blocks...). Media-library images appear as asset:<assetId>. |
| `assets` | object | yes | The media-library images the content uses, by assetId. |
| `audience` | object \| null | yes | — |
| `audience.kind` | "segment" | yes | — |
| `audience.segmentId` | string | yes | — |
| `scheduledAt` | string \| null | yes | — |
| `recipientCount` | integer \| null | yes | — |
| `updatedAt` | string | yes | — |
| `timeZone` | string | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `PLAN_REQUIRED` | 403 | — |
| `CAMPAIGN_NOT_FOUND` | 404 | — |
| `NOT_EDITABLE` | 409 | — |
| `INVALID_CONTENT` | 400 | — |
| `INVALID_AUDIENCE` | 400 | — |
| `SEGMENT_NOT_FOUND` | 404 | — |
| `ASSET_NOT_FOUND` | 404 | — |
| `ASSET_NOT_ALLOWED` | 422 | — |

```bash
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/campaigns/{campaignId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"audience":{"kind":"segment","segmentId":"seg_example01"}}'
```

```json
{
  "id": "camp_example01",
  "name": "October seasonal bake",
  "channel": "EMAIL",
  "status": "DRAFT",
  "content": {
    "subject": "October special: chestnut croissants are here",
    "body": "This month we're baking..."
  },
  "assets": {},
  "audience": {
    "kind": "segment",
    "segmentId": "seg_example01"
  },
  "scheduledAt": null,
  "recipientCount": 320,
  "updatedAt": "2026-10-06T08:00:00.000Z",
  "timeZone": "Asia/Taipei"
}
```
