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

# Errors

> HTTP status codes, the error response shape, validation errors, and the common errors from the platform API, the email send API and the SMTP relay.

Brudcast signals errors with the HTTP status code first. The body then explains what went wrong.
Branch on the status code, then on `code` when it's present, and never on the wording of
`message`.

## HTTP status codes

| Status       | Meaning                                                                                     | Retry?                                                         |
| ------------ | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `200`, `201` | Success                                                                                     |                                                                |
| `202`        | Accepted. The email send API queued your message                                            |                                                                |
| `400`        | The request broke a business rule, such as a missing subscription or a from-domain mismatch | No. Fix the request                                            |
| `401`        | The credential is missing or invalid                                                        | No. Fix the credential                                         |
| `402`        | Not enough credits in the wallet                                                            | After topping up                                               |
| `403`        | The credential is valid but not allowed to do this                                          | No                                                             |
| `404`        | The resource doesn't exist in your organization                                             | No                                                             |
| `422`        | The body or query string failed validation                                                  | No. Fix the fields in `errors`                                 |
| `429`        | Rate limit exceeded                                                                         | Yes, after waiting. See [Rate limits](/developers/rate-limits) |
| `500`        | Unexpected server error                                                                     | Yes, with backoff                                              |
| `503`        | A dependency is temporarily unavailable                                                     | Yes, with backoff                                              |

## Platform API errors

```json theme={"system"}
{
  "success": false,
  "message": "Resource not found",
  "code": "E_ROW_NOT_FOUND"
}
```

<ResponseField name="success" type="boolean" required>
  Always `false`.
</ResponseField>

<ResponseField name="message" type="string" required>
  What went wrong, in plain words.
</ResponseField>

<ResponseField name="code" type="string">
  A stable identifier to branch on. A few responses leave it out, so always check the status code
  as well.
</ResponseField>

Some codes add fields that tell you exactly what to fix:

| Code                        | Status             | Meaning                                                                                                                 | Extra fields                                                 |
| --------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `E_VALIDATION_ERROR`        | 422                | One or more fields are invalid                                                                                          | `errors[]`                                                   |
| `E_UNAUTHORIZED_ACCESS`     | 401                | The API key is missing, unknown, revoked or expired, or the request came from an IP address outside the key's allowlist |                                                              |
| `E_UNAUTHORIZED`            | 401                | The account behind the credential is no longer active                                                                   |                                                              |
| `E_FORBIDDEN`               | 403                | This endpoint can't be used with an API key                                                                             |                                                              |
| `E_INSUFFICIENT_SCOPE`      | 403                | The key lacks the scope the endpoint needs                                                                              | `requiredScope`, `grantedScopes`                             |
| `E_ROW_NOT_FOUND`           | 404                | No such resource in your organization                                                                                   |                                                              |
| `E_TOO_MANY_REQUESTS`       | 429                | Rate limit exceeded                                                                                                     | `retryAfter` (seconds)                                       |
| `E_BUSINESS_RULE_VIOLATION` | 400, sometimes 404 | The request conflicts with the resource's current state                                                                 |                                                              |
| `E_CAMPAIGN_NOT_SENDABLE`   | 400                | A campaign failed its send checklist on one or more channels                                                            | `errors[]`, each with `channel`, `requirement` and `message` |
| `INSUFFICIENT_CREDITS`      | 402                | The wallet can't cover the send                                                                                         | `required`, `available`, `bucket`, `shortfall`               |
| `SUBSCRIPTION_REQUIRED`     | 400                | The organization has no active subscription for a product this needs                                                    | `productSlug`, `productSlugs`                                |
| `PRODUCT_DISABLED`          | 400                | A product this touches has been disabled. Existing data stays readable                                                  | `productSlug`, `productSlugs`                                |
| `QUOTA_EXCEEDED`            | 400                | The plan's quota for a metric is used up                                                                                | `metric`, `productSlug`, `limit`, `used`, `requested`        |

One response without a `code` is worth knowing about: sending an `X-Organization-Id` header that
doesn't match the key's organization returns `403` with
`This API key cannot act on the requested organization`.

### Validation errors

A `422` lists every invalid field, so you can fix them all at once:

```json theme={"system"}
{
  "success": false,
  "message": "The payload is invalid",
  "code": "E_VALIDATION_ERROR",
  "errors": [
    {
      "field": "emailAddress",
      "rule": "email",
      "message": "The emailAddress field must be a valid email address"
    }
  ]
}
```

| Field     | Meaning                                                                  |
| --------- | ------------------------------------------------------------------------ |
| `field`   | The field that failed, using the request's own field name                |
| `rule`    | The validation rule it broke, such as `required`, `email` or `maxLength` |
| `message` | A readable explanation                                                   |

## Email send API errors

The [email send API](/developers/sending/http-api) returns `status: false` and a `message`:

```json theme={"system"}
{
  "status": false,
  "message": "Domain mail.example.com is not registered"
}
```

| Status | Message                                                                            | Fix                                                                                                |
| ------ | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| 400    | `From address domain 'x' does not match sending key domain 'y'`                    | Send from the key's own domain, or use a key for the other domain                                  |
| 400    | `Domain x is not registered`                                                       | Add the domain in **Channels > Email** first                                                       |
| 400    | `From address domain 'x' is not owned by the sending key's organization`           | The domain belongs to another organization. Use one of yours                                       |
| 401    | `Unauthorized`                                                                     | Send `Authorization: Bearer <key>` with a valid, active key                                        |
| 403    | `Sending is disabled for this account: <reason>`                                   | See the reasons below                                                                              |
| 422    | `Validation error`                                                                 | Fix the fields listed in `errors`                                                                  |
| 429    | `Too many requests, please try again later.`                                       | Your IP address exceeded a request limit. Wait for `Retry-After`                                   |
| 429    | `Too many failed attempts, please try again later`                                 | More than 20 failed authentications from your IP address in 15 minutes. Fix the key, then wait     |
| 429    | `Rate limit exceeded: perMinute` (or `perSecond`, `perHour`, `perDay`, `perMonth`) | Your organization's sending limit for that window is used up                                       |
| 500    | `An unexpected error occurred`                                                     | Also returned for a body that isn't valid JSON or is over 10 MB. Check the request before retrying |

The `<reason>` in a `403` is one of:

* `account suspended`: the organization is suspended. Email
  [support@brudcast.com](mailto:support@brudcast.com).
* `account status cannot be verified right now`: a temporary check failed. Retry with backoff.
* `sending domain is suspended for bounce or complaint rates`: the domain's bounce or complaint rate
  crossed the limits in [Deliverability best practices](/deliverability/best-practices).

A validation error lists each problem with its `path` in the body:

```json theme={"system"}
{
  "status": false,
  "message": "Validation error",
  "errors": [
    {
      "code": "custom",
      "path": ["text"],
      "message": "At least one of 'text' or 'html' is required"
    }
  ]
}
```

## Using the wrong credential on the wrong host

A platform API key and a sending key look identical: both start with `bk_live_` and are followed by
64 hex characters. Only the host and the header tell them apart, so sending the wrong one is the
most common cause of an `Unauthorized` you can't explain.

| What you sent                                     | Where          | What comes back                                                                                                                                 |
| ------------------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| A **sending key** as `X-API-Key`                  | Platform API   | `401 Unauthorized access` — the key isn't a platform key, so it's simply unknown                                                                |
| A **sending key** as `Authorization: Bearer`      | Platform API   | `401 Unauthorized access`. The value starts with `bk_`, so it's accepted as an API key and then fails lookup. The message is the same as a typo |
| A **platform API key** as `Authorization: Bearer` | Email send API | `401 Unauthorized` — the send API only knows sending keys                                                                                       |
| A **platform API key** without `Bearer `          | Email send API | `401 Unauthorized`. The send API reads only the `Authorization` header and needs the `Bearer ` prefix                                           |

Because both failures read the same as a mistyped key, check the host before you check the key:

* `core-service.prod.brudcast.com` takes a **platform API key** in `X-API-Key`.
* `mailing-service.prod.brudcast.com` takes a **sending key** in `Authorization: Bearer`.

Keep them in differently named variables — `BRUDCAST_API_KEY` and `BRUDCAST_SENDING_KEY` — so the
two can't be swapped by accident. See [Authentication](/developers/authentication) and
[Sending keys](/developers/sending-keys).

## SMTP relay replies

The [SMTP relay](/developers/sending/smtp-relay) answers with standard SMTP reply codes. `4xx`
replies are temporary and a well-behaved client retries them. `5xx` replies are permanent.

| Reply                                                      | Meaning                                                                                  |
| ---------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `535 Invalid credentials`                                  | Wrong username or password, or the SMTP user is deactivated                              |
| `535 Authentication failed`                                | The check couldn't complete. Retry later                                                 |
| `538 Error: Must issue a STARTTLS command first`           | On ports `587`, `2525` and `25`, run `STARTTLS` before `AUTH`                            |
| `550 MAIL FROM domain does not match authenticated domain` | The envelope sender must be on the SMTP user's domain                                    |
| `550 Domain x not registered`                              | The domain isn't set up in Brudcast                                                      |
| `550 Sending is disabled for this account: <reason>`       | Same reasons as the send API's `403`                                                     |
| `451 Rate limit exceeded: <window>`                        | Your organization's limit for that window is used up. Your client should queue and retry |
| `550 Missing From address`                                 | The message has no `From` header                                                         |
| `451 Failed to queue message`                              | A temporary failure. Your client should retry                                            |

<Columns cols={2}>
  <Card title="Rate limits" icon="gauge" href="/developers/rate-limits">
    Limits on every surface and how to back off.
  </Card>

  <Card title="Response format" icon="braces" href="/developers/response-format">
    The success envelope and pagination.
  </Card>
</Columns>
