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

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.

export_unavailableHTTP 409

The job finished and reported ready, but its file could not be read back from storage, so it was never delivered. This is not expiry — the retention window had not run out. It is a failure on our side.

What to do: Nothing to do about the credits: the reservation is returned automatically when this is detected, and error.details.creditsRefunded reports how many came back. The job then reads failed with an explanation rather than continuing to advertise itself as downloadable. Run the search again to rebuild the file, and quote the X-Request-Id if it recurs.

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.

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.

What to do: Retry. Nothing was charged.

service_unavailableHTTP 503

The requested API subsystem is temporarily unable to serve the request.

What to do: Retry with backoff. Nothing was charged. Quote the request id if the problem persists.

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, taxonomy, location lookups and the analytics plane cost nothing, so you can evaluate the data before you spend a credit.