# API Response Warnings

> How the Broadcast API tells you when a request succeeded but did something different from what you asked for, using the warnings array in responses.

Source: https://sendbroadcast.net/docs/api-response-warnings

Sometimes an API request succeeds, but the outcome differs from what you asked for: a parameter was ignored, an optional flow was skipped, or a record was changed in a way you may not have intended. Instead of failing the request or staying silent, the Broadcast API explains the difference in a `warnings` array included in the response.

This is a cross-cutting convention: **any endpoint that accepts input may include `warnings` in its response.**

## When warnings appear

A warning is added whenever the API accepts your request but the result diverges from the input. The most common situations:

1. **A parameter was not recognized:** for example, a misspelled field, or a parameter placed at the wrong nesting level. The API processes the rest of the request and tells you which parameters it ignored. This applies at every depth: a key inside a settings object, a segment rule or an email header, an entry of the wrong type in a list such as `segment_ids`, and a key inside a block document. The message lists the keys that are recognized at that level.
2. **A requested flow was skipped:** for example, requesting double opt-in for a subscriber who is already confirmed. The request succeeds, but no confirmation email is sent.
3. **A side effect you may not have intended:** for example, creating a subscriber that already exists reactivates the existing record.
4. **A webhook your request triggered had a problem:** the change is saved and the request succeeds, but a webhook it triggered couldn't be recorded, was held, or is waiting on an endpoint that isn't responding. See [Webhook warnings](#webhook-warnings).
5. **A better-suited endpoint exists for what you appear to be doing:** for example, updating a subscriber with `is_active: false` deactivates the record, but only the dedicated unsubscribe endpoint records an unsubscribe date and adds the address to the suppression list.
6. **Another setting decided the value:** for example, a block email must have an unsubscribe link, so `html_body_add_footer: false` sent with a document that has no footer block and no `{{ unsubscribe_url }}` link is set back to `true`. See [The footer of an email](#the-footer-of-a-block-email).
7. **Content was changed before it was stored or sent:** for example, a text block keeps only simple formatting, so a `style` attribute on a link is removed. See [Block documents](#block-documents).

Warnings apply to update endpoints exactly as they do to create endpoints: an unrecognized or misplaced parameter in a `PATCH`/`PUT` request is ignored and reported the same way.

If everything in your request was applied exactly as sent, the `warnings` key is absent from the response.

## Response format

Warnings appear as a top-level `warnings` array alongside the normal response body. Each warning has a machine-readable `code`, a human-readable `message`, and, where relevant, the `param` it refers to:

```json
{
  "id": 123,
  "email": "jane@example.com",
  "is_active": true,
  "confirmed_at": "2026-01-10T09:30:00Z",
  "warnings": [
    {
      "code": "double_opt_in_skipped",
      "message": "Subscriber is already confirmed; no confirmation email was sent."
    }
  ]
}
```

A warning never changes the HTTP status code. A `200`/`201` with warnings is still a success: the warnings only describe how the outcome differs from the request.

Warnings appear only on successful (2xx) responses. A failed request performs no work, so there is no divergence to report: the `error`/`errors` key is the explanation.

A small number of endpoints respond with an empty body (for example, adding or removing a sequence subscriber); those responses cannot carry a `warnings` array, but webhook problems still reach you through the `Broadcast-Warning` header (see [Webhook warnings](#webhook-warnings)).

List endpoints can warn too: an unusable filter value (for example, a `created_after` date that cannot be parsed) is reported with `parameter_ignored` while the rest of the query is answered normally.

Warnings describe what happened during **this request**. Retrying an identical request may return different warnings (or none) because the first request changed state. For example, a create that reactivates an inactive subscriber warns `subscriber_reactivated`; the identical retry finds an already-active subscriber and raises no warning.

## Warning codes

| Code | Meaning |
|------|---------|
| `unrecognized_parameter` | A parameter in your request was not recognized and was ignored. The `param` field names it. Check the spelling and nesting level against the endpoint's documentation. |
| `double_opt_in_skipped` | Double opt-in was requested, but the subscriber is already confirmed, so the confirmation step was skipped. On subscriber creation no confirmation email is sent; on transactional sends the email is sent normally without a confirmation step. |
| `subscriber_reactivated` | The create call matched an existing inactive or unsubscribed subscriber, and that record was reactivated. |
| `subscriber_suppressed` | The create or resubscribe call resubscribed a subscriber whose address is on a suppression list, so the subscriber stays inactive. Remove the address from the suppression list first, then activate the subscriber. |
| `use_dedicated_endpoint` | The update was applied, but a dedicated endpoint exists for this operation with additional behavior you likely want. The `message` names it. |
| `parameter_ignored` | A recognized parameter was not applied in this context: for example, a value only used by another flow, a field that cannot change in the record's current state, or a filter value that could not be interpreted. The `message` explains why. |
| `webhook_not_recorded` | Your change was saved, but a webhook it triggered could not be recorded and will not be delivered. The app logs have the details. |
| `webhook_held` | Your change was saved, but a webhook it triggered could not be queued. It is held and will be sent later. |
| `parameter_overridden` | The server applied a different value than the one you sent: for example, creating a subscriber always activates it, even if you pass `is_active: false`. The response body shows the value actually applied. |
| `provider_setup_failed` | Your change was saved, but Broadcast could not apply it at the email provider (for example, the Postmark webhook for `postmark_subscription_sync`). The `message` has the provider's error. Try the change again later, or check the provider account. |
| `content_sanitized` | HTML you sent in a block document will be changed when the email is rendered: tags, attributes or links that the block does not allow are removed. The `message` names what is removed, never the content itself. |

The set of codes will grow over time as more endpoints adopt richer warnings. Treat unknown codes as informational: log them, but do not fail on them.

### Stability guarantees

- **`code` values are stable.** Codes are append-only: new codes may be added, but existing codes are never renamed or removed. Branch your logic on `code`.
- **`message` text is not stable.** Messages are written for humans and may be reworded at any time; never parse or match on them.
- **`param` uses dot-path notation** relative to the request body or query string, e.g. `subscriber.double_opt_in`, including keys inside option hashes, e.g. `double_opt_in.replyto` for a misspelled `reply_to`. A position in a list is a number: `segment.segment_groups_attributes.0.segment_rules_attributes.1.negate`, or `broadcast.blocks_json.blocks.2.styles.bg`. It names the parameter only; submitted values are never echoed back.
- **Tolerate the unknown.** Your client must accept responses with no `warnings` key, warnings with codes it has not seen, and warnings with additional fields added later.

### Example: misplaced parameter

A common mistake is nesting flow-level options inside the `subscriber` object. Flow options such as `double_opt_in` and `confirmation_template_id` are top-level parameters (see [Subscribers API](https://sendbroadcast.net/docs/api-subscribers)):

```http
POST /api/v1/subscribers

{
  "subscriber": {
    "email": "jane@example.com",
    "double_opt_in": true
  }
}
```

The subscriber is created, but `double_opt_in` is not a subscriber attribute, so no confirmation flow runs. The response makes that visible:

```json
{
  "id": 124,
  "email": "jane@example.com",
  "warnings": [
    {
      "code": "unrecognized_parameter",
      "param": "subscriber.double_opt_in",
      "message": "double_opt_in is not a subscriber attribute and was ignored. Pass it as a top-level parameter instead."
    }
  ]
}
```

### Example: deactivating instead of unsubscribing

Update endpoints raise warnings the same way. Here, setting `is_active: false` via the generic update succeeds, but unsubscribing through the dedicated endpoint is almost always what you want: it also records the unsubscribe date and adds the address to the channel's suppression list:

```http
PATCH /api/v1/subscribers?email=jane@example.com

{
  "subscriber": {
    "is_active": false
  }
}
```

```json
{
  "id": 123,
  "email": "jane@example.com",
  "is_active": false,
  "unsubscribed_at": null,
  "warnings": [
    {
      "code": "use_dedicated_endpoint",
      "param": "subscriber.is_active",
      "message": "Subscriber was deactivated, but no unsubscribe date or suppression entry was recorded. To unsubscribe a subscriber, use POST /api/v1/subscribers/unsubscribe instead."
    }
  ]
}
```

### Example: a topic the subscriber turned off

(Requires Broadcast 2.49 or later)

Only the subscriber can turn on a topic they turned off. Here, `newsletter` is a tag and `webinars` is the tag of a topic the subscriber turned off from the unsubscribe link:

```http
POST /api/v1/subscribers/add_tag

{
  "email": "jane@example.com",
  "tags": ["newsletter", "webinars"]
}
```

`newsletter` is added and `webinars` is not. The rest of the request is applied, and the warning names the topic:

```json
{
  "id": 123,
  "email": "jane@example.com",
  "tags": ["newsletter"],
  "warnings": [
    {
      "code": "parameter_ignored",
      "param": "tags",
      "message": "Webinars was turned off by the subscriber. Only the subscriber can turn it back on, from their preference page."
    }
  ]
}
```

Create and update do the same for `subscriber.tags`, and for a custom data topic in `subscriber.custom_data`. See [Topics the subscriber turned off](https://sendbroadcast.net/docs/api-subscribers#topics-the-subscriber-turned-off).

## Block documents

A block document (`content_type: "blocks"` with `blocks_json`) is checked when you create or update a broadcast, template, sequence step or transactional email. The document is always saved as you sent it; the warnings tell you what the renderer will ignore or change:

| What you sent | Code |
|------|------|
| A key that the block type, `theme` or `meta` does not know, such as `styles.bg` on a text block | `unrecognized_parameter` |
| A block `type` that is not supported. The block is kept but does not render. | `unrecognized_parameter` |
| A colour the renderer does not accept (it accepts hex values such as `#1a73e8`, `transparent` and colour names). The default colour is used instead; for `styles.linkColor` the theme link colour is used. | `parameter_ignored` |
| A link the renderer does not accept (it accepts `https`, `http`, `mailto`, `tel`, `#anchors` and Liquid tags such as `{{ unsubscribe_url }}`) | `parameter_ignored` |
| HTML that a text, image + text or footer block does not allow. These blocks keep `p`, `br`, `strong`, `em`, `u`, `ul`, `ol`, `li` and links with an `href`. `b` and `i` become `strong` and `em`. | `content_sanitized` |
| Scripting or unsupported markup in an `html` block, such as `script` tags or `onclick` attributes | `content_sanitized` |
| `html_body_add_footer` that does not match the document | `parameter_overridden` |
| `body` sent together with a document. The body is rendered from the document. | `parameter_overridden` |

To colour the links in a text or image + text block, set `styles.linkColor` on the block, not a `style` attribute on the link. A link takes the first colour that is set:

1. The `styles.linkColor` value of the block (Broadcast 2.48.0 or later).
2. The `theme.link` value of the document.
3. The link colour of the brand kit.

Use an `html` block for markup the other blocks do not allow.

```http
POST /api/v1/broadcasts

{
  "broadcast": {
    "subject": "October news",
    "content_type": "blocks",
    "blocks_json": {
      "version": 1,
      "blocks": [
        {
          "id": "intro",
          "type": "text",
          "props": { "html": "<p>Read <a href=\"https://example.com\" style=\"color:#c00\">the post</a></p>" },
          "styles": { "bg": "#fff8e1" }
        }
      ]
    }
  }
}
```

```json
{
  "id": 185,
  "warnings": [
    {
      "code": "unrecognized_parameter",
      "param": "broadcast.blocks_json.blocks.0.styles.bg",
      "message": "bg is not a recognized key in styles for a text block and was ignored. Recognized keys: align, color, linkColor, size, lineHeight, padding."
    },
    {
      "code": "content_sanitized",
      "param": "broadcast.blocks_json.blocks.0.props.html",
      "message": "A text block keeps only p, br, strong, em, u, ul, ol, li and links (a with href), and was changed. Removed: style attributes. Use an html block for custom markup. Links take the linkColor style of a text or imageText block, else the theme.link colour of the document, or the brand kit link colour when it has none."
    }
  ]
}
```

### The footer of an email

Every marketing email has one unsubscribe link. Broadcast adds the channel footer only when the email has no unsubscribe link of its own. From Broadcast 2.46.0, the rule is the same for rich text, HTML and block emails:

1. If the email has its own unsubscribe link, the channel footer is not added, whatever `html_body_add_footer` says. An own link is a footer block that shows its unsubscribe link, or a link to `{{ unsubscribe_url }}` that every subscriber sees.
2. If a rich-text or block email has no own link, the channel footer is added.
3. If an HTML email has no own link, `html_body_add_footer` decides (default `true`).

A link counts only when every subscriber sees it. A link inside a Liquid tag such as `{% if %}`, in a block with `show_if_tag`, or in a column block with `show_if_tag`, does not count. A footer block with **Show unsubscribe link** off (`"showUnsubscribe": false`) does not count either.

If you send a value that does not apply, the response carries a `parameter_overridden` warning. For example, `html_body_add_footer: false` on a block email with no footer block and no `{{ unsubscribe_url }}` link is set to `true`, and `html_body_add_footer: true` on an email with its own link is not applied.

**Emails sent with the unsubscribe link off:** the send decides, not the template. An email sent with its unsubscribe link off gets no channel footer, no unsubscribe link and no `List-Unsubscribe` header, whatever `html_body_add_footer` says. A footer block in it shows no unsubscribe link. `html_body_add_footer` applies only to an email sent with the link on, so a stored `true` does not mean that every send gets the channel footer. Where to switch the link off:

- **Confirmation from an opt-in form:** turn off the form's unsubscribe link setting (`automation_settings.include_unsubscribe_link_in_confirmation: false`). It is on by default.
- **Confirmation from the subscribers API:** send `double_opt_in: { include_unsubscribe_link: false }`. Without it, the link is on. The template must be a confirmation template (`template_purpose: "confirmation"`); any other template is refused with a 422.
- **Sequence step:** set the step's `include_unsubscribe_link: false`. It is on by default, and it decides for a step that uses a template too.
- **Transactional email:** the link is off by default. It is on only when you send `include_unsubscribe_link: true`.

A block email whose footer block has **Show unsubscribe link** off, such as a double opt-in confirmation, gets a `parameter_overridden` warning on `html_body_add_footer` that lists these settings. The warning for a confirmation template says this too.

```http
POST /api/v1/broadcasts

{
  "broadcast": {
    "subject": "October news",
    "content_type": "blocks",
    "html_body_add_footer": false,
    "blocks_json": {
      "version": 1,
      "blocks": [
        { "id": "body", "type": "text", "props": { "html": "<p>Hello</p>" } },
        {
          "id": "our-footer",
          "type": "html",
          "props": { "html": "<p>Example GmbH, Musterstraße 1, Berlin. <a href=\"{{ unsubscribe_url }}\">Unsubscribe</a></p>" }
        }
      ]
    }
  }
}
```

This broadcast goes out with your footer only, and the response carries no warning.

## Errors, not warnings

A value that the API cannot store at all is an error, not a warning. For example, an unknown value for a field with a fixed list of values, such as `vendor`, `content_type` or a sequence step `action`, returns `422 Unprocessable Content`. The message names the parameter and lists the accepted values, and nothing is saved.

## Webhook warnings

A change made through the API is never rejected because its webhook couldn't be sent. If a webhook the request triggered had a problem, the request still succeeds with a 2xx status and tells you:

- JSON object responses include the problem in the `warnings` array, with one of the `webhook_*` codes above.
- Every successful response also carries a `Broadcast-Warning` header, including responses with an empty body such as adding a subscriber to a sequence. Several warnings are separated by commas.

```http
HTTP/1.1 200 OK
Broadcast-Warning: webhook_held; event="subscriber.updated"
```

```json
{
  "id": 123,
  "email": "jane@example.com",
  "warnings": [
    {
      "code": "webhook_held",
      "message": "Your change was saved, but the subscriber.updated webhook could not be queued. It is held and will be sent later."
    }
  ]
}
```

Requests that commonly trigger webhooks, such as creating or updating a subscriber or adding one to a sequence, can carry these warnings. When nothing went wrong with the webhook, neither the warning nor the header is added. A webhook that is held only because your endpoint isn't responding doesn't produce a warning either: the event is recorded and will be delivered. Endpoint outages are shown on the Webhooks page instead (see [Webhooks](https://sendbroadcast.net/docs/webhooks#endpoint-health-and-queued-deliveries)).

## Handling warnings in your integration

- **Log them.** Warnings are the fastest way to spot an integration bug: a typo'd field name or misplaced parameter shows up immediately instead of failing silently.
- **Do not treat them as errors.** The request succeeded, and the response body reflects the actual state. Warnings are advisory context, not a signal to retry.
- **Ignore unknown keys and codes.** Your client should tolerate both the `warnings` key being absent and warning codes it has not seen before.

## Backwards compatibility

The `warnings` array is additive and optional:

- No existing response fields change: names, types, and values stay the same.
- HTTP status codes are unchanged.
- The key is only present when at least one warning was raised.

Clients that read specific fields from responses (the common case, including the official Ruby SDK) are unaffected. If you validate responses against a strict schema, allow additional properties, per standard REST evolution rules, the API may add new response fields over time.
