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
account_disabledHTTP 401
The key resolved, but the owning account has been disabled.
What to do: Ask a team owner or Boardwalk support to restore the account. Rotating the key does not change the account state.
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.
credits_exhaustedHTTP 402
The credit pool cannot afford even one billable record. Paid balances reset on their billing schedule; trial credits are a one-off allowance.
What to do: Use the free count endpoint to narrow the selection, wait for the paid reset, or add credits. Nothing was delivered and nothing was charged.
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.
export_expiredHTTP 409
The file lived out its retention window and was swept. expiresAt on the job is the deadline, and it had passed.
What to do: Run the search again with format=csv to build a fresh file. Records you already hold unchanged are free to receive again, so a rebuild usually costs less than the original — price it first with /projects/count.
export_not_readyHTTP 409
The export job exists but has not finished building. format=csv is asynchronous — the queue response is a 202 with an id, not a file.
What to do: Poll GET /api/v1/exports/{id} until status is ready, then download. The job carries creditsReserved from the moment it is queued, so there is nothing further to spend.
forbiddenHTTP 403
The authenticated account is not permitted to perform this operation, and no more specific policy code applies.
What to do: Check the key's account and plan permissions. Nothing was charged.
geographic_access_deniedHTTP 403
A requested state or county falls outside the geography licensed to this pool.
What to do: Remove the denied geography or expand the plan. details.deniedStates and details.allowedStates carry the id, name and abbreviation of each, so you do not have to resolve ids to answer "what can I query instead?" (deniedStateIds and allowedStateIds remain alongside them). To avoid the error entirely, call the free GET /locations/states and read the entitled flag on each state before you build a filter.
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.
key_expiredHTTP 401
A rotated key reached the end of its 24-hour overlap window.
What to do: Switch to the replacement key issued at rotation. The old key cannot be reactivated, but no account or subscription change is required.
method_not_allowedHTTP 405
The path exists, but the request used an HTTP method that route does not accept.
What to do: Read the Allow response header and retry with one of the listed methods. The request was not processed and nothing was charged.
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 the applicable allowance in a trailing 60 seconds. Valid keys are limited per key; missing credentials use a source-IP bucket and unknown or unusable Boardwalk-shaped keys use a credential-digest bucket.
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, taxonomy, location lookups and the analytics plane cost nothing, so you can evaluate the data before you spend a credit.