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
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
{
"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.
| Field | Meaning |
|---|---|
error.code | A stable machine token. Branch on this. |
error.message | Human copy. May be reworded; do not parse it. |
error.param | The offending parameter, where there is one. |
error.details | Structured context — the quote on a 402, the problem list on a 400. |
error.docsUrl | A link to the entry below. |
error.requestId | The 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.
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.
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.
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.