Skip to main content

Errors

Every error code the API emits, when it happens, and what to do about it.

Every error has the same shape, and every one carries a docsUrl pointing at its own entry on this page.

What a request you cannot afford looks like

bash
curl 'https://api.boardwalkai.com/api/v1/projects/search?states=UT&assetClasses=Apartment%20Building&dateRange=all&limit=100' \
  -H 'Authorization: Bearer bwk_live_YOUR_KEY'

Response · HTTP 402

json
{
  "error": {
    "code": "insufficient_credits",
    "message": "This request would deliver 5,000 records and cost 5,000 credits. You have 500.",
    "param": null,
    "details": {
      "quote": {
        "matchCount": 5000,
        "matchCountIsExact": true,
        "creditRate": 1,
        "creditsRequired": 5000,
        "creditsRemaining": 500,
        "affordableRecordCount": 500,
        "countUrl": "https://api.boardwalkai.com/api/v1/projects/count?states=UT&assetClasses=Apartment%20Building&dateRange=all"
      }
    },
    "docsUrl": "https://boardwalkai.com/docs/api/errors#insufficient_credits",
    "requestId": "req_01K1QF3M0000EXAMPLE0001"
  }
}

The response shape on this page is the one the endpoint builds — the keys, their nesting and their types are asserted against the shipping code by a test. The numbers inside it are the scenario being walked through, not a measurement of the corpus.

FieldMeaning
error.codeA stable machine token. Branch on this.
error.messageHuman copy. May be reworded; do not parse it.
error.paramThe offending parameter, where there is one.
error.detailsStructured context — the quote on a 402, the problem list on a 400.
error.docsUrlA link to the entry below.
error.requestIdThe same id as the X-Request-Id header, our logs and your usage row. Quote it to support.

You can send your own X-Request-Id and we will echo it, which lets you join our logs to yours.

Every code

alert_limit_reachedHTTP 403

The account already holds the maximum number of active alerts.

What to do: Pause or delete one. error.details.maxActiveAlerts states the ceiling.

enrichment_incompatible_representationHTTP 400

include=contacts was combined with a representation that carries no contact fields — asking to pay for enrichment and then not receive it.

What to do: Use standard or full.

enrichment_requires_asyncHTTP 400

include=contacts was sent to a synchronous route. Enrichment calls out to third-party sources and cannot complete inside a request.

What to do: Submit the same search to POST /projects/ai-search/jobs with include: "contacts" and poll for the result. error.details names the endpoint.

idempotency_in_flightHTTP 409

The same Idempotency-Key is currently being processed by another request.

What to do: Wait for Retry-After seconds and retry. Do not fan out the same key in parallel; the point of the key is that only one of them charges.

idempotency_key_reuseHTTP 409

An Idempotency-Key was reused with a different request body or filter set. Replaying a key is safe; changing the request under it is not.

What to do: Use a fresh key for a different request. Keys are scoped to the credit pool, so a teammate's key cannot collide with yours by accident and cannot be replayed by you either.

insufficient_creditsHTTP 402

The request would have cost more credits than the pool has left. Nothing was delivered and nothing was charged — the charge is attempted inside a transaction that is rolled back, so a 402 leaves your balance exactly where it was.

What to do: Read error.details.quote. It carries creditsRequired, creditsRemaining, affordableRecordCount and a countUrl — a free URL that prices this exact filter set. Narrow the filters until creditsRequired fits, or top up. An agent can recover from this response without a human, which is why the URL is in the body.

internal_errorHTTP 500

Something failed on our side.

What to do: Retry. Nothing was charged — 4xx and 5xx responses never spend credits. Quote the X-Request-Id header when you contact support; it is the same id in your response body, in our logs and on the usage row.

invalid_cursorHTTP 400

A /projects/sync cursor was malformed, or was issued against a different filter set or a different entitlement scope than the one now being requested. A cursor is only lossless for the query it was minted for.

What to do: error.details.recovery says it: repeat the request without the cursor. Records you already hold are not re-billed, so restarting a walk is cheap.

invalid_filterHTTP 400

One or more filter parameters were rejected: an unknown parameter name, a value outside the enum, a non-numeric range bound, or a place name that resolved ambiguously. Unknown parameters are refused, never ignored — silently dropping one would bill you for a broader query than you asked for.

What to do: error.details.problems[] names every offending parameter with its own message. The filter reference lists every accepted name, alias and value.

invalid_requestHTTP 400

A parameter was present but unusable — an unknown representation, a sort on the sync feed, a malformed body. error.param names it.

What to do: Read error.param and error.details.supported, which lists the accepted values where there is a closed set.

missing_api_keyHTTP 401

No credential was presented, or the value was not a Boardwalk API key.

What to do: Send Authorization: Bearer bwk_live_... or X-API-Key: bwk_live_.... A web session cookie or a login JWT is not a credential for this API; there is exactly one, and it is an API key.

rate_limit_exceededHTTP 429

More requests than your per-key allowance in a trailing 60 seconds.

What to do: Back off for Retry-After seconds. X-RateLimit-Remaining and X-RateLimit-Reset are on every response, so a well-behaved client never needs to see this code.

rate_limiter_unavailableHTTP 503

The rate limiter itself could not be reached. The request was refused rather than waved through.

What to do: Retry after Retry-After. Nothing was charged.

resource_not_foundHTTP 404

The project, document, alert or job id does not exist, or is not visible to this key. Both cases return 404 — an id that exists but belongs to someone else must not be distinguishable from one that does not exist.

What to do: Check the id. If you are following a documents[].meetingDocumentId from a record, use that value rather than documents[].id.

search_unavailableHTTP 503

The search backend could not be reached, so no count and no results could be produced. This is deliberately not reported as zero matches: "we found nothing" and "we could not look" are opposite answers, and a customer narrowing against a false zero would conclude their market was empty.

What to do: Retry. Nothing was charged.

subscription_requiredHTTP 402

The key is valid and the account has no active plan or trial — usually a lapsed subscription. The key is not revoked. It keeps working the moment the plan is reactivated.

What to do: Reactivate the plan. You do not need to re-issue keys or redeploy.

sync_tie_unresolvedHTTP 503

More records share one last-updated timestamp than a single sync page can position, so advancing the cursor could not be done without risking a skip.

What to do: error.details.suggestion: split the walk by geography and run one cursor per slice. Nothing was charged.

unauthorizedHTTP 401

The key was presented but did not resolve to an account — most often a revoked key, or one whose 24-hour rotation grace window has closed.

What to do: Mint a new key on your profile. A rotating key's responses carry X-Boardwalk-Key-Rotating: true and a Warning header naming the moment it stops, so this is avoidable.

Ready to make a call?

A free Boardwalk trial includes API access and a sandbox key. Counting, filtering, the location tree and the analytics plane cost nothing, so you can evaluate the data before you spend a credit.