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

# Error codes

> Every error code the API can return: what causes it and what to do. Codes are stable; we add new ones and never rename one.

Every error response carries a `code`, and its `doc_url` links to that code on this page. Branch on `code`, never on `message`. The [errors guide](/guides/errors) covers the error format and how to handle errors in code.

Codes are a stable contract: we add new ones and never rename or repurpose one. If you meet a code that isn't listed here, handle it by its `type` and HTTP status.

## 400 and 422: the request

<div id="INVALID_REQUEST" style={{ scrollMarginTop: '8rem' }} />

### INVALID\_REQUEST

`400 Bad Request` · type `invalid_request` · [Open page](/errors/INVALID_REQUEST)

The request as a whole was refused before we read it, today only because the body is over 2 MB.

**When it happens**

The request body is larger than 2 MB, judged by its `Content-Length` header or by what actually arrived. A quote with 25 policies and every field at its longest is about 1.3 MB, so a real request shouldn't come close.

**What to do**

Check that you aren't sending something you didn't mean to, such as an encoded document. Don't retry the same request.

<div id="INVALID_JSON" style={{ scrollMarginTop: '8rem' }} />

### INVALID\_JSON

`400 Bad Request` · type `invalid_request` · [Open page](/errors/INVALID_JSON)

The request body isn't valid JSON.

**When it happens**

The body of a `POST` couldn't be parsed as JSON: a trailing comma, a single-quoted string, or a body built by string concatenation. (An empty body is read as `{}` and then fails validation instead.)

**What to do**

Serialize the body with your language's JSON library, and send `Content-Type: application/json`.

<div id="VALIDATION_FAILED" style={{ scrollMarginTop: '8rem' }} />

### VALIDATION\_FAILED

`422 Unprocessable Entity` · type `invalid_request` · [Open page](/errors/VALIDATION_FAILED)

The body is valid JSON but breaks the endpoint's rules. `details` lists every problem at once.

**When it happens**

One or more fields are missing, have the wrong type or format, are out of range, or aren't fields at all. `POST /v1/quotes` checks the whole body and reports **every** problem in `details`, each with its own `code`, `param` and `message`; the top-level `param` and `message` describe the first.

Common causes:

* money sent as a JSON number (`46100`) instead of a string (`"46100.00"`);
* a rate sent as a percentage (`25`) instead of a fraction (`0.25`);
* `insured.address.state` that isn't a two-letter USPS state code (detail code `INVALID_FORMAT`). A full state name gets `Use the two-letter state code, e.g. "TX" for Texas.`; anything else gets `Must be a two-letter US state code, e.g. "TX".` A valid code for a state outside the program isn't an error: it's a `201` ineligible quote;
* `minimum_earned` with both or neither of `rate` and `amount`;
* a coverage `type` that isn't on the [list](/guides/coverage-types), or a misspelled field name;
* a blank `broker_email`;
* more than 25 policies (one `TOO_MANY` detail, before anything else is checked).

**What to do**

Fix each field named in `details` and send the request again. A request that fails validation never uses up its `Idempotency-Key`, so you can reuse the key. It does count toward your [rate limits](/guides/rate-limits), so don't retry it unchanged.

<div id="INVALID_API_VERSION" style={{ scrollMarginTop: '8rem' }} />

### INVALID\_API\_VERSION

`400 Bad Request` · type `invalid_request` · [Open page](/errors/INVALID_API_VERSION)

The `Bridgeline-Version` header isn't a real date in `YYYY-MM-DD` form.

**When it happens**

You sent a `Bridgeline-Version` header that isn't a valid calendar date, such as `2026-9-23`, `v1` or `2026-02-30`.

**What to do**

Send `Bridgeline-Version: 2026-09-23`, or leave the header out to use the current version. See [versioning](/changelog#versioning).

<div id="IDEMPOTENCY_KEY_INVALID" style={{ scrollMarginTop: '8rem' }} />

### IDEMPOTENCY\_KEY\_INVALID

`400 Bad Request` · type `invalid_request` · [Open page](/errors/IDEMPOTENCY_KEY_INVALID)

The `Idempotency-Key` header isn't 1 to 255 printable ASCII characters.

**When it happens**

The `Idempotency-Key` header is empty, longer than 255 characters, or contains characters outside printable ASCII.

**What to do**

Use a UUID v4 (36 characters). See [idempotency](/guides/idempotency).

<div id="IDEMPOTENCY_KEY_REQUIRED" style={{ scrollMarginTop: '8rem' }} />

### IDEMPOTENCY\_KEY\_REQUIRED

`400 Bad Request` · type `invalid_request` · [Open page](/errors/IDEMPOTENCY_KEY_REQUIRED)

This endpoint requires an `Idempotency-Key` header. No current endpoint does.

**When it happens**

Reserved for endpoints where a duplicate would matter most, such as ones that send documents to a borrower. **No endpoint available today requires a key**, so you won't see this code yet.

**What to do**

Send an `Idempotency-Key` on every write now, and you'll never see it. See [idempotency](/guides/idempotency).

<div id="INVALID_PAGINATION" style={{ scrollMarginTop: '8rem' }} />

### INVALID\_PAGINATION

`400 Bad Request` · type `invalid_request` · [Open page](/errors/INVALID_PAGINATION)

A list request's `limit` isn't a whole number from 1 to 100. No list endpoint exists yet.

**When it happens**

Reserved for list endpoints, which take `limit` (1 to 100, default 20) and `starting_after`. **No list endpoint is available today**, so you won't see this code yet.

**What to do**

When list endpoints ship, send a `limit` between 1 and 100.

## 401: the key

<div id="INVALID_API_KEY" style={{ scrollMarginTop: '8rem' }} />

### INVALID\_API\_KEY

`401 Unauthorized` · type `authentication` · [Open page](/errors/INVALID_API_KEY)

The API key is missing, malformed or not recognized.

**When it happens**

* There's no `Authorization` header, or it doesn't start with `Bearer `.
* The key is truncated or mistyped. Every key carries a checksum, so a typo is caught straight away.
* The key doesn't exist, or its prefix doesn't match its mode.

**What to do**

Check you're sending the whole key, exactly as it was shown when it was created, as `Authorization: Bearer bl_live_...`. A lost key can't be recovered: an agency admin can revoke it and create a new one under **API Keys** in the portal.

<div id="KEY_REVOKED" style={{ scrollMarginTop: '8rem' }} />

### KEY\_REVOKED

`401 Unauthorized` · type `authentication` · [Open page](/errors/KEY_REVOKED)

The API key has been revoked.

**When it happens**

An agency admin revoked this key in the portal. Revocation is immediate and permanent.

**What to do**

Ask an agency admin to create a new key under **API Keys** in the portal, and replace the old one in your secret store.

<div id="KEY_EXPIRED" style={{ scrollMarginTop: '8rem' }} />

### KEY\_EXPIRED

`401 Unauthorized` · type `authentication` · [Open page](/errors/KEY_EXPIRED)

The API key has passed its expiry date.

**When it happens**

The key was created with an expiry date, and that date has passed.

**What to do**

Ask an agency admin to create a new key. To rotate without downtime, create the new key before the old one expires; a key can be used alongside another.

## 403: permission

<div id="API_ACCESS_NOT_ENABLED" style={{ scrollMarginTop: '8rem' }} />

### API\_ACCESS\_NOT\_ENABLED

`403 Forbidden` · type `permission` · [Open page](/errors/API_ACCESS_NOT_ENABLED)

API access isn't turned on for your agency.

**When it happens**

Bridgeline hasn't enabled API access for your agency yet, or it has been turned off. During the beta, API access is by invitation. When it's off, every key your agency holds gets this answer.

**What to do**

Contact your Bridgeline contact. Links to interactive quotes you've already sent keep working while access is off; see [links outlive API access](/guides/hosted-quote-page#links-outlive-api-access).

<div id="MISSING_SCOPE" style={{ scrollMarginTop: '8rem' }} />

### MISSING\_SCOPE

`403 Forbidden` · type `permission` · [Open page](/errors/MISSING_SCOPE)

The API key lacks a scope this endpoint needs.

**When it happens**

The endpoint needs a scope the key wasn't given, for example `quotes:write` to create a quote. The message names the missing scopes.

**What to do**

Scopes are set when a key is created. Ask an agency admin to create a key with the scopes you need, then revoke the old one. See [scopes](/authentication#scopes).

<div id="BROKER_NOT_ACTIVE" style={{ scrollMarginTop: '8rem' }} />

### BROKER\_NOT\_ACTIVE

`403 Forbidden` · type `permission` · [Open page](/errors/BROKER_NOT_ACTIVE)

The `broker_email` you sent isn't an active broker at your agency.

**When it happens**

You named a broker in `broker_email`, and they aren't an active broker at the key's agency. We give the same answer whether they're inactive, invited but not yet active, or at another agency.

**What to do**

Check the email matches the broker's login in the Bridgeline portal. If they're new, an agency admin can invite or activate them there. Or leave `broker_email` out to act as the key's default broker.

<div id="ACTING_BROKER_NOT_ALLOWED" style={{ scrollMarginTop: '8rem' }} />

### ACTING\_BROKER\_NOT\_ALLOWED

`403 Forbidden` · type `permission` · [Open page](/errors/ACTING_BROKER_NOT_ALLOWED)

A broker key can only act as its own broker.

**When it happens**

The request came from a **broker key** and named a different broker in `broker_email`.

**What to do**

Leave `broker_email` out, or use an **agency key**, which can act as any active broker at your agency. See [key types](/authentication#key-types).

<div id="DEFAULT_BROKER_INACTIVE" style={{ scrollMarginTop: '8rem' }} />

### DEFAULT\_BROKER\_INACTIVE

`403 Forbidden` · type `permission` · [Open page](/errors/DEFAULT_BROKER_INACTIVE)

The API key's default broker is no longer active.

**When it happens**

The broker this key acts as by default has been deactivated, and the request didn't name another broker. Reads always use the default broker, so they fail too. We never move a request to someone else silently.

**What to do**

Ask an agency admin to create a new key with an active default broker. With an agency key, a write can also name an active broker in `broker_email`.

<div id="TEST_MODE_NOT_ENABLED" style={{ scrollMarginTop: '8rem' }} />

### TEST\_MODE\_NOT\_ENABLED

`403 Forbidden` · type `permission` · [Open page](/errors/TEST_MODE_NOT_ENABLED)

Test mode isn't turned on for your agency, so its test keys are refused.

**When it happens**

You used a test key (`bl_test_`), and test mode isn't enabled for your agency, or has been turned off. Bridgeline enables test mode agency by agency. An agency admin who tries to create a test key in the portal gets the same answer.

Test mode is enabled separately from live API access: this code is only ever about test keys, and your live keys keep working either way.

**What to do**

Ask your Bridgeline contact to enable test mode for your agency. See [test and live mode](/concepts/test-and-live-mode).

<div id="TEST_MODE_ONLY" style={{ scrollMarginTop: '8rem' }} />

### TEST\_MODE\_ONLY

`403 Forbidden` · type `permission` · [Open page](/errors/TEST_MODE_ONLY)

A test helper was called with a live key. Test helpers need a test key.

**When it happens**

You called a test helper, an endpoint under `/v1/test_helpers/`, with a live key (`bl_live_`). Test helpers act out what a person would do, such as your client tapping **I'm interested**, so they only work on test data, with a test key.

**What to do**

Call it with a test key (`bl_test_`), on a quote that key created. See [test and live mode](/concepts/test-and-live-mode).

## 404, 405 and 409

<div id="NOT_FOUND" style={{ scrollMarginTop: '8rem' }} />

### NOT\_FOUND

`404 Not Found` · type `not_found` · [Open page](/errors/NOT_FOUND)

There's no object with this id that your key can see.

**When it happens**

The id is malformed, doesn't exist, or belongs to something your key can't see. All of these get the same answer, so an id never reveals whether a deal exists elsewhere:

* a quote from another agency;
* a quote in the other mode (a test key reading a live quote, or the reverse);
* a colleague's quote, read with a **broker key**, which sees only its own broker's quotes.

**What to do**

Check the id (quote ids start with `qte_` and are 26 characters), and that the key is the right mode and type. An agency key can see every quote in the agency.

<div id="METHOD_NOT_ALLOWED" style={{ scrollMarginTop: '8rem' }} />

### METHOD\_NOT\_ALLOWED

`405 Method Not Allowed` · type `invalid_request` · [Open page](/errors/METHOD_NOT_ALLOWED)

The endpoint exists, but not with this HTTP method.

**When it happens**

Reserved. Today, a request with an unsupported method on an existing path gets a `405` with no JSON body rather than this error.

**What to do**

Check the method against the [API reference](/api-reference/introduction): `POST` to create, `GET` to read.

<div id="QUOTE_EXPIRED" style={{ scrollMarginTop: '8rem' }} />

### QUOTE\_EXPIRED

`409 Conflict` · type `conflict` · [Open page](/errors/QUOTE_EXPIRED)

The quote is past its `expires_at`, so it can't get a new link or a response.

**When it happens**

The quote's `expires_at` has passed, and you called either:

* `POST /v1/quotes/{id}/hosted-link`. Quotes expire 30 days after they're created, and a new link never extends that.
* `POST /v1/test_helpers/quotes/{id}/interest`, on a test quote. An expired quote takes no response, just as its interactive quote doesn't.

**What to do**

Create a new quote with the same details, and send your client its `hosted_url` (or, in test mode, act out the response on the new quote).

<div id="QUOTE_ALREADY_INTERESTED" style={{ scrollMarginTop: '8rem' }} />

### QUOTE\_ALREADY\_INTERESTED

`409 Conflict` · type `conflict` · [Open page](/errors/QUOTE_ALREADY_INTERESTED)

The test quote already has your client's response. A quote takes one response only.

**When it happens**

You called `POST /v1/test_helpers/quotes/{id}/interest` on a test quote whose `status` is already `interested`, whether from an earlier call or from someone tapping **I'm interested** on its interactive quote. Like the interactive quote itself, a quote takes one response only.

**What to do**

Read the response that's already recorded with [`GET /v1/quotes/{id}`](/api-reference/quotes/retrieve). To act it out again, create a new test quote.

<div id="IDEMPOTENCY_KEY_REUSED" style={{ scrollMarginTop: '8rem' }} />

### IDEMPOTENCY\_KEY\_REUSED

`409 Conflict` · type `conflict` · [Open page](/errors/IDEMPOTENCY_KEY_REUSED)

This `Idempotency-Key` was already used for a different request.

**When it happens**

Within the last 24 hours, the same key was sent with a different request: another path, another body, or another acting broker. Bodies are compared byte for byte, so re-serializing the same data can count as different (keys reordered, spacing changed).

**What to do**

Use a new key for a new request. To retry, resend the exact bytes you sent the first time. See [idempotency](/guides/idempotency).

<div id="IDEMPOTENCY_REQUEST_IN_PROGRESS" style={{ scrollMarginTop: '8rem' }} />

### IDEMPOTENCY\_REQUEST\_IN\_PROGRESS

`409 Conflict` · type `conflict` · [Open page](/errors/IDEMPOTENCY_REQUEST_IN_PROGRESS)

The first request with this `Idempotency-Key` is still running.

**When it happens**

You retried while the original request with the same key was still being processed, usually because the first attempt timed out on your side.

**What to do**

Wait for the `Retry-After` header (1 second) and retry with the same key. You'll get the original response once it finishes.

## 429 and 5xx

<div id="RATE_LIMITED" style={{ scrollMarginTop: '8rem' }} />

### RATE\_LIMITED

`429 Too Many Requests` · type `rate_limit` · [Open page](/errors/RATE_LIMITED)

Too many requests: you're over a per-minute or daily limit.

**When it happens**

The key went over 60 requests a minute, or `POST /v1/quotes` went over 500 quotes a day for the key or 2,000 a day for your agency. Every authenticated request counts, including ones that fail validation.

**What to do**

Wait the number of seconds in `Retry-After`, then retry with the same `Idempotency-Key`. For a daily limit, that's until midnight UTC. See [rate limits](/guides/rate-limits).

<div id="INTERNAL" style={{ scrollMarginTop: '8rem' }} />

### INTERNAL

`500 Internal Server Error` · type `api_error` · [Open page](/errors/INTERNAL)

Something went wrong on our side.

**When it happens**

An unexpected error. We log every one with its request id.

**What to do**

Retry with the same `Idempotency-Key`, with backoff. Usually nothing was stored and the retry runs normally.

If a retry returns this same `500` with the header `Idempotent-Replayed: true`, that's the stored outcome of a request that may have taken effect: stop retrying that key, and handle it like [`IDEMPOTENCY_RECORD_LOST`](/errors/IDEMPOTENCY_RECORD_LOST). If it keeps happening, contact us with the `request_id`.

<div id="IDEMPOTENCY_RECORD_LOST" style={{ scrollMarginTop: '8rem' }} />

### IDEMPOTENCY\_RECORD\_LOST

`500 Internal Server Error` · type `api_error` · [Open page](/errors/IDEMPOTENCY_RECORD_LOST)

The original request with this key may have finished, but its response was lost. This key will never succeed.

**When it happens**

You retried with an `Idempotency-Key` whose original request started more than two minutes ago and never recorded a response, for example because the server stopped after doing the work but before saving the answer. We can't replay a response we don't have, and we won't run the request again in case it already took effect.

**What to do**

Don't retry with this key; it will keep failing. There's no `Retry-After`.

* **`POST /v1/quotes`:** create the quote again with a new key. A duplicate quote is harmless: nothing is sent to anyone, and it expires on its own.
* **`POST /v1/quotes/{id}/hosted-link`:** call it again with a new key. That revokes whichever link is current and returns a fresh one.

See [idempotency](/guides/idempotency#when-the-response-was-lost).

<div id="SERVICE_UNAVAILABLE" style={{ scrollMarginTop: '8rem' }} />

### SERVICE\_UNAVAILABLE

`503 Service Unavailable` · type `api_error` · [Open page](/errors/SERVICE_UNAVAILABLE)

We can't price right now. Nothing was stored; retry shortly.

**When it happens**

`POST /v1/quotes` couldn't read something it needs to price safely, such as current market rates or pricing settings. We refuse to quote rather than guess.

**What to do**

Wait the number of seconds in `Retry-After` (30), then retry with the same `Idempotency-Key`. Nothing was stored, so the retry runs normally.


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