> ## 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.

# Render Campaign HTML

> Render a campaign to email-ready HTML for an embedded preview

Render a campaign to the exact email-safe HTML that would be sent. Use this to show a visual preview inside your own dashboard or email builder without duplicating Sequenzy's rendering logic.

The render applies everything the send path applies: your email theme and font, company branding and footer, localization, block visibility conditions, product recommendation blocks, and merge tags.

<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">
  Personalize as this stored subscriber. Use this or `subscriber`, not both.
  Because the rendered HTML then carries that subscriber's details, this field
  also requires the `subscribers:read` scope.
</ParamField>

<ParamField body="subscriber" type="object">
  Personalize as an ad-hoc contact that does not need to exist in your account.
  Accepts `email` (required), `firstName`, `lastName`, and `customAttributes`.
  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">
  Force a localization locale instead of deriving it from the contact.
</ParamField>

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

<ParamField body="tracking" type="boolean" default="false">
  Apply your company's auto-UTM link decoration, as a real send would. Defaults
  to `false` so the preview shows your own clean URLs.
</ParamField>

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/campaigns/camp_abc123/render" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriber": {
      "email": "anna@example.com",
      "firstName": "Anna"
    },
    "variables": { "plan": "Pro" },
    "tracking": false
  }'
```

## Personalization

Without `subscriberId` or `subscriber`, the campaign renders for a sample contact and merge tags resolve to empty values. The response's `personalized` field tells you which happened.

Only a stored subscriber (`subscriberId`) can resolve subscriber-scoped content: block visibility conditions, product recommendations, and a working unsubscribe link. An inline `subscriber` covers name and attribute merge tags.

Passing `subscriberId` puts that subscriber's data into the returned HTML, so it needs the `subscribers:read` scope on top of `campaigns:read`. Rendering for a sample contact or an inline `subscriber` needs only `campaigns:read`.

## What is not in the HTML

Per-send click-redirect wrapping, the open tracking pixel, and unsubscribe click tracking are applied at send time against a real email send record. A preview has no such record, so those are never present in the returned HTML - emitting placeholder versions would give you URLs that 404. The `tracking` flag therefore controls auto-UTM decoration only, which is genuinely applied at render time.

## Responses

<ResponseExample>
  ```json 200 theme={null}
  {
    "success": true,
    "html": "<!DOCTYPE html><html>...</html>",
    "subject": "Anna, your Pro plan is ready",
    "previewText": "Everything you unlocked this month",
    "locale": "en",
    "personalized": true,
    "trackingApplied": false,
    "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 404 theme={null}
  {
    "error": "Campaign not found"
  }
  ```
</ResponseExample>
