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

# Check Campaign

> Find broken links, placeholders and content problems before sending a campaign

Run the pre-send email check on a saved campaign: the same rules as the email checker in the editor, plus live verification of every link and image. Use it in your own approval flow, or before scheduling a campaign through the API.

<Note>
  This endpoint is read-only. It requires the `campaigns:read` scope and never
  sends, schedules, or modifies anything. It uses `POST` only so personalization
  input can travel in a request body.
</Note>

## Request

<ParamField path="campaignId" type="string" required>
  Campaign ID.
</ParamField>

<ParamField body="subscriberId" type="string">
  Check as this stored subscriber: picks their localization and reports merge
  tags and conditions that would not resolve for them. Link and content rules
  always run on the email as written. Use this or `subscriber`, not both.
  Requires the `subscribers:read` scope as well.
</ParamField>

<ParamField body="subscriber" type="object">
  Check as an ad-hoc contact that does not need to exist in your account.
  Accepts `email` (required), `firstName`, `lastName`, `customAttributes`, and
  `tags`. Use this or `subscriberId`, not both.
</ParamField>

<ParamField body="variables" type="object">
  Extra merge variables layered over the contact's attributes.
</ParamField>

<ParamField body="locale" type="string">
  Check a specific localization instead of deriving it from the contact.
</ParamField>

<ParamField body="variantId" type="string">
  Check a specific A/B test variant of this campaign.
</ParamField>

<ParamField body="links" type="boolean" default="true">
  Verify every link and image over the network. Pass `false` for a fast,
  rules-only check that makes no requests.
</ParamField>

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/campaigns/camp_abc123/check" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

## What gets checked

Links and images are collected from every version recipients can receive: each A/B variant and each translation. Pass `variantId` or `locale` to check only that version.

**Links and images, live.** Every web link and image is requested from our servers (HEAD, then GET when a server refuses HEAD), following up to 8 redirects. Each one comes back with a `status`:

| Status | Meaning |
| - | - |
| `ok` | Answered with a success status, after any redirects. |
| `broken` | Page not found (404/410), the domain doesn't exist, an SSL certificate problem, a redirect loop, or a rejected request. Images that don't return an image also count. |
| `invalid` | Can never work for recipients: a rule already found it unusable (for example localhost, a private address, a mistyped `https://` or no `https://` at all), or the domain points to a private address. |
| `server_error` | The server answered with a 5xx error. Often temporary. |
| `unreachable` | No answer in time, or the connection was refused or reset. |
| `restricted` | The site refused an automated check (for example 401, 403 or 429), or redirects to itself to set a cookie. It may work fine in a browser, so it is never counted as broken. |
| `personalized` | Built from merge tags filled in per recipient, such as `{{event.orderUrl}}`. Only its syntax is checked. |
| `not_checked` | `mailto:` and `tel:` links, links not reached before the 25-second time limit, or `links` was `false`. |

`{{company.url}}` is resolved from your company website before checking. If no website is set, those links are reported as `links.missing-website`.

**Links, without a request.** Placeholder links (`#`, a bare `https://`), buttons with no link, script links, links missing `https://` or relative links, mistyped schemes such as `htps://`, unencoded spaces, incomplete domains, localhost and private addresses, tunnel and deploy-preview URLs, staging subdomains, placeholder domains such as `example.com`, `http://` links, link shorteners, raw IP addresses, link text that shows a different domain than the link goes to, invalid `mailto:`/`tel:` links, and unclosed merge tags in URLs.

**Images.** Broken or non-image URLs, images over 1 MB, embedded base64 images, local file paths, placeholder image services, and missing alt text.

**Content.** Leftover placeholder copy (lorem ipsum, `[First Name]`, TODO), name merge tags without a fallback, spam-trigger wording, excessive capitals, profanity, too many or no links, a missing unsubscribe link in marketing email, empty conditional branches, and broken merge tag syntax.

**Subject and preview text.** Length, placeholder text, a fake `Re:` or `Fwd:` prefix, spam-trigger wording, capitals, punctuation, emoji, and preview text that repeats the subject.

**Rendered HTML.** Gmail clipping risk, button contrast and tap-target size, and features specific email clients drop.

Sender settings (From and Reply-To) are not part of this check.

## Response

<ResponseField name="success" type="boolean">
  `true` when the check ran.
</ResponseField>

<ResponseField name="score" type="integer">
  0 to 100 across subject (25%), preview text (15%) and content (40%), rescaled
  to 100 because sender settings aren't part of this check. The editor also
  weighs your sender (20%), so its score can differ slightly for the same email.
</ResponseField>

<ResponseField name="grade" type="string">
  `A`, `B`, `C`, `D` or `F`.
</ResponseField>

<ResponseField name="placement" type="string">
  Predicted inbox tab: `Primary`, `Promotions` or `Spam`.
</ResponseField>

<ResponseField name="categories" type="object">
  `subject`, `preview` and `content`, each with `score` and `maxScore`.
</ResponseField>

<ResponseField name="issues" type="object[]">
  Findings, errors first. Each has `category`, `severity` (`error` to fix before
  sending, `warning` to review, `info` for tips) and `message`, plus a stable
  `rule` ID such as `links.broken` or `content.placeholder-text`. When the
  finding has one location, `blockId` names the block to edit and `url` the link
  or image concerned.
</ResponseField>

<ResponseField name="links" type="object[]">
  Every link and image: `url`, `kind` (`link` or `image`), `label`, `blockId`,
  `blockType`, `status`, `httpStatus`, `finalUrl` (after redirects), `message`,
  and `findings` from the rules that need no request.
</ResponseField>

<ResponseField name="linkSummary" type="object">
  Counts: `total`, `checked`, `ok`, `broken` (broken and invalid), `warnings`
  (server\_error and unreachable), `restricted` and `notChecked` (personalized
  and not\_checked).
</ResponseField>

<ResponseField name="linksChecked" type="boolean">
  `false` when you passed `links: false`.
</ResponseField>

<ResponseField name="subject" type="string">
  Subject line with merge tags resolved for the checked contact.
</ResponseField>

<ResponseField name="previewText" type="string | null">
  Preview text with merge tags resolved, or `null` when unset.
</ResponseField>

<ResponseField name="locale" type="string">
  Localization the check used.
</ResponseField>

<ResponseField name="unresolvedMergeTags" type="object[]">
  Merge tags that did not resolve, as on the [render
  endpoint](/api-reference/campaigns/render).
</ResponseField>

<ResponseField name="unevaluatedConditions" type="object[]">
  Block conditions this check could not decide, as on the [render
  endpoint](/api-reference/campaigns/render).
</ResponseField>

<ResponseField name="entity" type="object">
  What was checked: `type`, `id` and `variantId`.
</ResponseField>

Results for the same URL are cached for up to 10 minutes (1 minute for failures), so repeating a check right after fixing a broken link can briefly return the old answer. Live checks are limited to 30 calls a minute per API key and company, and 60 a minute per API key across companies; past that you get `429`, and `links: false` still works.

<ResponseExample>
  ```json 200 theme={null}
  {
    "success": true,
    "score": 79,
    "grade": "C",
    "placement": "Promotions",
    "categories": {
      "subject": { "score": 100, "maxScore": 100 },
      "preview": { "score": 80, "maxScore": 100 },
      "content": { "score": 65, "maxScore": 100 }
    },
    "issues": [
      {
        "category": "content",
        "severity": "error",
        "rule": "links.broken",
        "message": "Broken link: \"Shop the sale\" (acme.com/sale) - Page not found (404).",
        "blockId": "block-hero",
        "url": "https://acme.com/sale"
      },
      {
        "category": "content",
        "severity": "error",
        "rule": "links.missing-scheme",
        "message": "Missing https:// - email clients won't open this: \"new arrivals\" (www.acme.com/new).",
        "blockId": "block-intro",
        "url": "www.acme.com/new"
      }
    ],
    "links": [
      {
        "url": "https://acme.com/sale",
        "kind": "link",
        "label": "Shop the sale",
        "blockId": "block-hero",
        "blockType": "hero",
        "status": "broken",
        "httpStatus": 404,
        "finalUrl": null,
        "message": "Page not found (404)",
        "findings": []
      }
    ],
    "linkSummary": {
      "total": 6,
      "checked": 4,
      "ok": 3,
      "broken": 1,
      "warnings": 0,
      "restricted": 0,
      "notChecked": 2
    },
    "linksChecked": true,
    "subject": "New arrivals picked for you",
    "previewText": "Fresh styles and restocked favorites",
    "locale": "en",
    "unresolvedMergeTags": [],
    "unevaluatedConditions": [],
    "entity": { "type": "campaign", "id": "camp_abc123", "variantId": null }
  }
  ```

  ```json 400 theme={null}
  {
    "error": "Provide either subscriberId or subscriber, not both."
  }
  ```

  ```json 401 theme={null}
  {
    "error": "Unauthorized"
  }
  ```

  ```json 403 theme={null}
  {
    "error": "API key is missing required scope: subscribers:read"
  }
  ```

  ```json 429 theme={null}
  {
    "error": "Too many link checks. Try again in a minute, or pass links: false for a rules-only check."
  }
  ```

  ```json 404 theme={null}
  {
    "error": "Campaign not found"
  }
  ```
</ResponseExample>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.