# Check who a campaign would reach before saving it

`POST /v1/campaigns/eligibility`

Requires an API key with the `campaigns:read` scope. [About keys and scopes](/docs/api/authentication)

Takes the body `POST /campaigns` takes and answers as `GET /campaigns/{id}/eligibility` would for that draft, with `campaignId: null`, so the composer can show the audience while it is edited. Nothing is saved.

## Request body

- `name` (string, required, 1–255 characters)
- `channels` (string[], optional, at least 1 item) — The channels to send on; each needs its own content. Defaults to `email`.
- `groupId` (string, optional, uuid) — Send to this group. Choose at most one audience.
- `segmentId` (string, optional, uuid) — Send to this segment. Choose at most one audience.
- `audienceRules` (object, optional) — Send to contacts matching these rules. Choose at most one audience.
  - One of: **Match all**
    - `all` (object[], required, at least 1 item, at most 20 items) — Every rule or nested group must match.
  - One of: **Match any**
    - `any` (object[], required, at least 1 item, at most 20 items) — At least one rule or nested group must match.
- `topicId` (string | null, optional, uuid) — Send only to contacts the topic reaches. Omit to reach everyone with consent.
- `messageLimits` (MessageLimitUseInput, optional)
  - `apply` (boolean, required)
  - `retryHours` (integer, optional, min 0, max 48)
- `utm` (object | null, optional) — UTM parameters added to the links in the email.
  - `enabled` (boolean, required)
  - `source` (string, optional, 1–100 characters)
  - `medium` (string, optional, 1–100 characters)
  - `campaign` (string, optional, 1–100 characters)
  - `content` (string, optional, 1–100 characters)
  - `term` (string, optional, 1–100 characters)
- `content` (object, optional, default `{}`) — The content for each selected channel. A draft may leave parts out.
  - `email` (object, optional) — Subject, preview text and sender. Needed to send once `channels` has `email`.
    - `subject` (string, optional, 1–998 characters) — The subject line. Merge tags are allowed.
    - `previewText` (string, optional, 1–255 characters) — Shown by inboxes after the subject. Merge tags are allowed; keep it near 100 characters.
    - `from` (string, optional, 1–320 characters) — The sender address, on a verified sending domain.
    - `replyTo` (string, optional, email) — Where replies go.
  - `templateSnapshot` (TemplateSnapshotInput, optional) — The email design. Needed to send once `channels` has `email`.
    - `schemaVersion` (integer, required, min > 0, max 1000) — The block format version; use the current one, 1.
    - `blocks` (EmailBlockInput[], required, at least 1 item, at most 200 items) — At most 200 blocks across the entire tree and five levels including the root. Footer blocks must remain at root. Known child fields are validated recursively.
      - `id` (string, required, 1–128 characters) — Identifies the block within the template; unique among its blocks.
      - `type` (string, required, 1–64 characters) — The block type: `header`, `heading`, `text`, `image`, `button`, `divider`, `spacer`, `columns`, `columns3`, `imagetext`, `imagegroup`, `boxed`, `quote`, `list`, `product`, `video`, `social`, `menu`, `html`, `section`, `footer`, `repeat`, `shared`. Each type reads its own fields next to `type`; an unknown type is kept but not validated.
      - `style` (EmailBlockStyleInput, optional) — Visual overrides for this block, such as colours, font and spacing.
        - `backgroundColor` (string, optional) — Fill behind the block.
        - `fontFamily` (string, optional, one of `Arial`, `Verdana`, `Georgia`, `Tahoma`, `Times New Roman`, `Courier New`, `Inter`, `Roboto`, `Open Sans`, `Lato`, `Montserrat`, `Poppins`, `DM Sans`, `Nunito`, `Work Sans`, `Oswald`, `Playfair Display`, `Merriweather`, `Lora`, `Libre Baskerville`, `EB Garamond`) — A font every client has (Arial, Verdana, Georgia, Tahoma, Times New Roman, Courier New), or a web font, which shows where the client loads it and falls back to a safe font elsewhere.
        - `fontSize` (integer, optional, min 12, max 72) — Text size in px, 12 to 72.
        - `mobileFontSize` (integer, optional, min 12, max 72) — Text size in px on phones (screens up to 620px), 12 to 72.
        - `padding` (object, optional) — Space inside the block in px, 0 to 100 per side; omitted sides keep their default.
        - `borderWidth` (integer, optional, min 0, max 10) — Border thickness in px; 0 for none.
        - `borderStyle` (string, optional, one of `solid`, `dashed`, `dotted`) — Border line style.
        - `borderColor` (string, optional) — Border colour.
        - `borderRadius` (integer, optional, min 0, max 100) — Corner rounding in px, 0 to 100.
        - `textColor` (string, optional) — Text colour.
        - `linkColor` (string, optional) — Colour of links in the text.
        - `letterSpacing` (number, optional, min -2, max 20) — Space between letters in px, such as 3 for a spaced-out eyebrow line.
        - `textTransform` (string, optional, one of `none`, `uppercase`) — Set to `uppercase` to capitalise all text.
        - `lineHeight` (number, optional, min 1, max 3) — A multiple of the font size.
        - `fontWeight` (string, optional, one of `regular`, `bold`) — Text weight.
        - `fontStyle` (string, optional, one of `normal`, `italic`) — Text style.
        - `inset` (boolean, optional) — Draw the background and border as a box within the content width, not across the whole row.
      - `visibleIf` (object, optional) — Show the block only to contacts these segment rules match, checked as each message is rendered. Omit to show it to everyone. Not allowed on the footer.
        - One of: **Match all**
        - One of: **Match any**
      - `showOn` (string, optional, one of `mobile`, `desktop`) — Show the block only on phones (screens up to 620px) or only on wider screens. Clients that ignore screen size show the desktop version, and so does plain text. Omit to show it everywhere. Not allowed on the footer.
      - `columns` (object[][], optional, at most 3 items) — Nested child block arrays: exactly two for columns, three for columns3. Each child follows this block contract. Omit to retain legacy text columns.
      - `blocks` (object[], optional, at most 200 items) — A section's child blocks, drawn inside its box; or a repeat block's, written out once per item of its `list`, where `{{ item.<field> }}` reads that item. Repeats do not nest.
    - `theme` (EmailThemeInput, required) — Theme values this email overrides.
      - `font` (string, optional, up to 200 characters) — Body font stack, such as `Inter, sans-serif`.
      - `hfont` (string, optional, up to 200 characters) — Heading font stack.
      - `bg` (string, optional) — Background of the email card, as hex.
      - `canvas` (string, optional) — Background behind the card, as hex.
      - `text` (string, optional) — Default text colour, as hex.
      - `accent` (string, optional) — Brand colour for buttons and highlights, as hex.
      - `link` (string, optional) — Link colour, as hex.
      - `width` (string, optional, one of `560px`, `600px`, `680px`) — Width of the email card on wide screens.
      - `radius` (string, optional, one of `0px`, `6px`, `12px`, `16px`, `24px`, `999px`) — Corner rounding of the email's card.
      - `btnRadius` (string, optional, one of `0px`, `6px`, `12px`, `16px`, `24px`, `999px`) — Button corners; `radius` rounds the email's card and, when this is unset, buttons.
      - `btn` (string, optional, one of `solid`, `outline`) — Button style.
    - `plainTextOverride` (string | null, optional, up to 100000 characters) — Plain text sent instead of the version generated from the blocks.
  - `pushProjectId` (string, optional, uuid) — The push app to send through. Needed to send once `channels` has `push`.
  - `push` (object, optional) — The notification content. Needed to send once `channels` has `push`.
    - `title` (string, optional, 1–100 characters) — The notification title (up to 100 characters).
    - `body` (string, optional, 1–500 characters) — The notification text (up to 500 characters).
    - `actionUrl` (string, optional, uri, up to 2048 characters) — The HTTPS page opened when the notification is tapped.
    - `imageUrl` (string, optional, uri, up to 2048 characters) — A large picture: web and Android show it; iOS needs the app's service extension.
    - `iconUrl` (string, optional, uri, up to 2048 characters) — Web only; iOS and Android show the app's own notification icon.
    - `data` (object, optional) — Up to 10 string key-value pairs passed to the app untouched, for deep links and in-app routing.
    - `buttons` (object[], optional, at least 1 item, at most 2 items) — Up to 2 action buttons.
      - `title` (string, required, 1–30 characters) — The button label.
      - `url` (string, required, uri, up to 2048 characters) — The HTTPS page the button opens.
    - `collapseKey` (string, optional) — A newer notification with the same key replaces the older one on the device.
    - `sound` (string, optional, 1–64 characters) — The name of a sound the app ships; the device's default if unset.
    - `badge` (integer, optional, min 0, max 99999) — The number shown on the iOS app icon.
    - `ttlSeconds` (integer, optional, min 0, max 2419200) — Seconds a provider keeps trying an offline device (up to 28 days); defaults to 86400 (one day).
    - `urgency` (string, optional, one of `high`, `normal`) — `high` wakes the device now; `normal` lets it wait for a good moment to save battery. Unset, a transactional push is `high` and any other `normal`.
    - `overrides` (object, optional) — Fields that differ on one platform, each replacing the shared one there (a whole `data` map, not merged key by key). On iOS and Android `actionUrl` may be an app link such as `myapp://orders`; the shared one and the web one must be HTTPS.
      - `ios` (object, optional) — Replaces shared fields on iOS.
        - `title` (string, optional, 1–100 characters) — The notification title (up to 100 characters).
        - `body` (string, optional, 1–500 characters) — The notification text (up to 500 characters).
        - `actionUrl` (string, optional, up to 2048 characters)
        - `imageUrl` (string, optional, uri, up to 2048 characters) — A large picture: web and Android show it; iOS needs the app's service extension.
        - `iconUrl` (string, optional, uri, up to 2048 characters) — Web only; iOS and Android show the app's own notification icon.
        - `data` (object, optional) — Up to 10 string key-value pairs passed to the app untouched, for deep links and in-app routing.
        - `buttons` (object[], optional, at least 1 item, at most 2 items) — Up to 2 action buttons.
        - `collapseKey` (string, optional) — A newer notification with the same key replaces the older one on the device.
        - `sound` (string, optional, 1–64 characters) — The name of a sound the app ships; the device's default if unset.
        - `badge` (integer, optional, min 0, max 99999) — The number shown on the iOS app icon.
        - `ttlSeconds` (integer, optional, min 0, max 2419200) — Seconds a provider keeps trying an offline device (up to 28 days); defaults to 86400 (one day).
        - `urgency` (string, optional, one of `high`, `normal`) — `high` wakes the device now; `normal` lets it wait for a good moment to save battery. Unset, a transactional push is `high` and any other `normal`.
        - `interruptionLevel` (string, optional, one of `passive`, `active`, `time-sensitive`) — How iOS presents it: `passive` arrives silently, `time-sensitive` breaks through Focus and needs the app's Time Sensitive Notifications capability.
        - `threadId` (string, optional) — Notifications with the same thread are grouped together in Notification Center.
      - `android` (object, optional) — Replaces shared fields on Android.
        - `title` (string, optional, 1–100 characters) — The notification title (up to 100 characters).
        - `body` (string, optional, 1–500 characters) — The notification text (up to 500 characters).
        - `actionUrl` (string, optional, up to 2048 characters)
        - `imageUrl` (string, optional, uri, up to 2048 characters) — A large picture: web and Android show it; iOS needs the app's service extension.
        - `iconUrl` (string, optional, uri, up to 2048 characters) — Web only; iOS and Android show the app's own notification icon.
        - `data` (object, optional) — Up to 10 string key-value pairs passed to the app untouched, for deep links and in-app routing.
        - `buttons` (object[], optional, at least 1 item, at most 2 items) — Up to 2 action buttons.
        - `collapseKey` (string, optional) — A newer notification with the same key replaces the older one on the device.
        - `sound` (string, optional, 1–64 characters) — The name of a sound the app ships; the device's default if unset.
        - `badge` (integer, optional, min 0, max 99999) — The number shown on the iOS app icon.
        - `ttlSeconds` (integer, optional, min 0, max 2419200) — Seconds a provider keeps trying an offline device (up to 28 days); defaults to 86400 (one day).
        - `urgency` (string, optional, one of `high`, `normal`) — `high` wakes the device now; `normal` lets it wait for a good moment to save battery. Unset, a transactional push is `high` and any other `normal`.
        - `channelId` (string, optional) — The Android notification channel to post in, which the app creates; it sets the sound and importance a person can tune, e.g. one for offers and one for orders.
      - `web` (object, optional) — Replaces shared fields on web.
        - `title` (string, optional, 1–100 characters) — The notification title (up to 100 characters).
        - `body` (string, optional, 1–500 characters) — The notification text (up to 500 characters).
        - `actionUrl` (string, optional, uri, up to 2048 characters) — The HTTPS page opened when the notification is tapped.
        - `imageUrl` (string, optional, uri, up to 2048 characters) — A large picture: web and Android show it; iOS needs the app's service extension.
        - `iconUrl` (string, optional, uri, up to 2048 characters) — Web only; iOS and Android show the app's own notification icon.
        - `data` (object, optional) — Up to 10 string key-value pairs passed to the app untouched, for deep links and in-app routing.
        - `buttons` (object[], optional, at least 1 item, at most 2 items) — Up to 2 action buttons.
        - `collapseKey` (string, optional) — A newer notification with the same key replaces the older one on the device.
        - `sound` (string, optional, 1–64 characters) — The name of a sound the app ships; the device's default if unset.
        - `badge` (integer, optional, min 0, max 99999) — The number shown on the iOS app icon.
        - `ttlSeconds` (integer, optional, min 0, max 2419200) — Seconds a provider keeps trying an offline device (up to 28 days); defaults to 86400 (one day).
        - `urgency` (string, optional, one of `high`, `normal`) — `high` wakes the device now; `normal` lets it wait for a good moment to save battery. Unset, a transactional push is `high` and any other `normal`.

## Returns

Returns `200 OK` with a `CampaignEligibility` in `data`. Otherwise returns an error; [Errors](/docs/api/errors) lists every code and which to retry.

- `campaignId` (string | null, required, uuid) — `null` when checked before saving (`POST /campaigns/eligibility`).
- `checkedAt` (string, required, date-time) — When the check ran.
- `audienceCount` (integer, required, min 0, max 9007199254740991) — Contacts in the audience, before any channel's exclusions.
- `blockers` (string[], required) — Blockers for the campaign as a whole.
- `issues` (object[], required) — What the draft still lacks, exactly as sending would refuse it (`DRAFT_INCOMPLETE`).
  - `path` (string, required) — The field that is incomplete, such as `content.email`.
  - `message` (string, required) — What is missing, in plain words.
- `channels` (object[], required) — One entry per selected channel.
  - One of: **Email channel**
    - `channel` ("email", required)
    - `eligibleRecipientCount` (integer, required, min 0, max 9007199254740991) — Contacts reachable on this channel after the exclusions.
    - `sendableRecipientCount` (integer, required, min 0, max 9007199254740991) — `eligibleRecipientCount` when nothing blocks the campaign anywhere, else 0.
    - `blockers` (string[], required) — What stops the email channel sending.
    - `excluded` (object, required) — Contacts left out of the audience, by reason.
      - `consentMissing` (integer, required, min 0, max 9007199254740991) — Contacts without email consent.
      - `topicUnsubscribed` (integer, required, min 0, max 9007199254740991) — Consented, but the campaign's topic does not reach them.
      - `suppressed` (integer, required, min 0, max 9007199254740991) — Contacts whose address is blocked, for example after a bounce.
      - `missingEmail` (integer, required, min 0, max 9007199254740991) — Contacts with no email address.
    - `senderVerified` (boolean, required) — Whether the sender domain is verified.
  - One of: **Push channel**
    - `channel` ("push", required)
    - `eligibleRecipientCount` (integer, required, min 0, max 9007199254740991) — Contacts reachable on this channel after the exclusions.
    - `sendableRecipientCount` (integer, required, min 0, max 9007199254740991) — `eligibleRecipientCount` when nothing blocks the campaign anywhere, else 0.
    - `blockers` (string[], required) — What stops the push channel sending.
    - `excluded` (object, required) — Contacts left out of the audience, by reason.
      - `consentMissing` (integer, required, min 0, max 9007199254740991) — Contacts without push consent.
      - `topicUnsubscribed` (integer, required, min 0, max 9007199254740991) — Consented, but the campaign's topic does not reach them.
      - `suppressed` (integer, required, min 0, max 9007199254740991) — Contacts blocked from push.
      - `missingInstallation` (integer, required, min 0, max 9007199254740991) — Contacts with no registered device.
    - `applicationReady` (boolean, required) — Whether the push app is set up to send.

## Example

```bash
curl -X POST https://api.kirora.io/v1/campaigns/eligibility \
  -H "Authorization: Bearer $KIRORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Weekend specials"
  }'
```

Response `200`:

```json
{
  "data": {
    "campaignId": "018f2a4e-6b1c-7d3e-9f40-3478765e5a6b",
    "checkedAt": "2030-03-14T09:30:00Z",
    "audienceCount": 0,
    "blockers": [
      "campaign_not_draft"
    ],
    "issues": [
      {
        "path": "path_example",
        "message": "message_example"
      }
    ],
    "channels": [
      {
        "channel": "email",
        "eligibleRecipientCount": 0,
        "sendableRecipientCount": 0,
        "blockers": [
          "missing_email_content"
        ],
        "excluded": {
          "consentMissing": 0,
          "topicUnsubscribed": 0,
          "suppressed": 0,
          "missingEmail": 0
        },
        "senderVerified": true
      }
    ]
  }
}
```
