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

# Errors

> HTTP status codes, error codes you can match on, and the shape of every error response.

Every error has a consistent JSON shape:

```json theme={null}
{
  "error": {
    "message": "Human-readable explanation, sometimes localized to the org's language.",
    "code": "MACHINE_READABLE_CODE"
  }
}
```

The HTTP status and the `error.code` are always consistent. Switch on `code`, surface `message` to your end user.

## Codes

| HTTP  | `error.code`       | Meaning                                                                                                       |
| ----- | ------------------ | ------------------------------------------------------------------------------------------------------------- |
| `400` | `BAD_REQUEST`      | Generic client error.                                                                                         |
| `400` | `VALIDATION_ERROR` | Request body or query failed validation. The `message` lists the offending fields.                            |
| `401` | `UNAUTHORIZED`     | Bearer missing, malformed, unknown, or expired.                                                               |
| `403` | `FORBIDDEN`        | Authenticated but not permitted: missing scope, plan limit reached, inactive key, or business rule violation. |
| `404` | `NOT_FOUND`        | Resource does not exist or is in a different organization.                                                    |
| `429` | `BAD_REQUEST`      | Rate limit exceeded. Retry after `Retry-After` seconds. See [Rate limits](/rate-limits).                      |
| `500` | `INTERNAL_ERROR`   | Unexpected server failure. Safe to retry idempotently after a short backoff.                                  |

## Plan-limit errors

Plan-limit `FORBIDDEN` errors carry a localized message such as *"You have reached the maximum number of leads for your plan."* — surface it to your end users as-is, since they're the ones who can take action (upgrade or remove records).

These come from the billing guards on:

* `POST /leads` — when `maxLeads` is exceeded.
* `POST /projects` — when `maxProjects` is exceeded.
* `POST /webhooks/subscriptions` — when `maxWebhookEndpoints` is exceeded.

## Validation errors

`VALIDATION_ERROR` messages list every field that failed in `path: reason` form, joined by `; `:

```json theme={null}
{
  "error": {
    "message": "Validation error: email: Invalid email; phone: String must be at most 50 characters",
    "code": "VALIDATION_ERROR"
  }
}
```

Parse the message if you need to surface field-level errors in your own UI, or just show the whole string.

## Idempotency and retries

* `5xx` and `429` are safe to retry. Use exponential backoff capped at the `Retry-After` value when present.
* `4xx` (other than `429`) means the request itself is wrong. Don't retry without changing it.
* `POST /webhooks/subscriptions` is idempotent when you pass `external_id` — see [Create a subscription](/api-reference/webhooks#create-a-subscription).
* Other writes are not idempotent. If you retry a `POST /leads` blindly, you'll create duplicates. Use [Search a lead](/api-reference/leads#search-a-lead) first to de-dupe.
