Troubleshooting
Every failure carries a stablecode and a requestId. Start there: read code from the Problem Details body and log X-Request-Id.
Authentication and scopes
401 invalid_api_key
401 invalid_api_key
Authorization: Bearer <key>; the key
matches blank_live_... for production or blank_test_... for staging and
development; expiresAt is in the future. Then rotate the key in
Settings and redeploy. See
Authentication.403 insufficient_scope
403 insufficient_scope
blank.identity.me(). See Scopes.The SDK throws when I construct a client
The SDK throws when I construct a client
BlankClient refuses an apiKey in a browser runtime, and rejects a
baseUrl that carries credentials, a query, or a fragment, or that is not
HTTPS outside localhost. Move the client to server code. If a browser needs
private data, proxy it through your own server.A browser request fails with a CORS error
A browser request fails with a CORS error
Access-Control-Allow-Origin, by design — an API key must never
reach a browser. Call them from your server.I cannot create an API key
I cannot create an API key
Requests and responses
404 not_found on a path that looks right
404 not_found on a path that looks right
https://api.blank.build/api/v2. There is no v1 contract and no
unversioned alias — a path outside /api/v2 will never serve the public
contract.422 validation_failed
422 validation_failed
errors[]: each entry has a path
such as body.predictedPriceInSol, a code, and a message. Request schemas
are strict, so an unexpected extra property is a validation failure, not a
silently ignored field.415 unsupported_content_type or 400 malformed_json
415 unsupported_content_type or 400 malformed_json
Content-Type: application/json and
well-formed JSON. 413 request_body_too_large means the body exceeded
1,048,576 bytes.Numbers look wrong or lose precision
Numbers look wrong or lose precision
BigInt or a decimal library — never
Number.The SDK throws BlankNetworkError on a successful-looking call
The SDK throws BlankNetworkError on a successful-looking call
BlankNetworkError for
aborts (request_aborted), per-attempt timeouts (request_timeout), and
transport failures (network_error). A BlankNetworkError means the
outcome is unknown — for a mutation, retry with the same
idempotencyKey exposed on the error.Pagination
400 cursor_invalid
400 cursor_invalid
Pagination never finishes
Pagination never finishes
page.hasMore is false or page.nextCursor is null. If you
use iterate(), it throws once maxPages (default 100, maximum 10,000) is
exceeded rather than looping forever — raise the bound deliberately or narrow
the query.A list looks truncated at 100 entries
A list looks truncated at 100 entries
Rate limits
429 rate_limit_exceeded
429 rate_limit_exceeded
Retry-After and wait. X-RateLimit-Policy names the policy that
tripped, and X-RateLimit-Limit / X-RateLimit-Remaining /
X-RateLimit-Reset describe the tightest dimension applying to you.Anonymous traffic is limited globally and per IP; authenticated traffic is
additionally limited per API key, per account, and per wallet — so more keys
on the same account will not raise an account-level ceiling. Limits are
environment configuration: read the headers, do not hardcode numbers.Idempotency and concurrency
400 idempotency_key_invalid
400 idempotency_key_invalid
Idempotency-Key of 8 to 128 printable ASCII
characters. The SDK generates one when you omit idempotencyKey, so this
usually means a hand-built HTTP request omitted the header.409 idempotency_key_reused
409 idempotency_key_reused
409 idempotency_request_in_progress
409 idempotency_request_in_progress
Retry-After: 1 window and
retry with the same key. Do not switch keys — that would create a second
mutation.I do not know whether my mutation succeeded
I do not know whether my mutation succeeded
Idempotent-Replayed: true. Generate and persist the key before the
request, so a crash mid-call still leaves you able to reconcile.412 precondition_failed
412 precondition_failed
If-Match did not match the resource’s current version — someone else
changed it first. Re-read the resource, re-apply your change on top of the
new state, and retry with the new version.Price predictions
404 prediction_intent_not_found
404 prediction_intent_not_found
409 prediction_intent_expired or prediction_intent_consumed
409 prediction_intent_expired or prediction_intent_consumed
403 prediction_wallet_mismatch
403 prediction_wallet_mismatch
walletAddress in the body must equal the wallet pinned to the API key. Read
it from blank.identity.me() rather than configuring it separately.409 prediction_round_not_open
409 prediction_round_not_open
open, before locksAt.
Re-read the round; if it has moved to locked, wait for the next one.409 prediction_already_submitted
409 prediction_already_submitted
blank.predictions.get(predictionId).409 prediction_username_required or prediction_wallet_not_verified
409 prediction_username_required or prediction_wallet_not_verified
blank.predictions.eligibility(mint, roundId).409 prediction_insufficient_balance
409 prediction_insufficient_balance
balanceRaw and evaluationSlot on
the eligibility response.409 prediction_token_not_enabled
409 prediction_token_not_enabled
422 prediction_price_out_of_range
422 prediction_price_out_of_range
predictedPriceInSol must be a positive canonical decimal string — not "0"
— with at most 12 integer digits and 18 fractional digits. Send a string, not
a number.409 prediction_exact_values_locked, or entries show no values
409 prediction_exact_values_locked, or entries show no values
listRoundPredictions returns
{ visibility: "aggregate", participantCount } and standings return this
error. After lock, entries return visibility: "exact". Your own prediction
is always readable via getOwnedPrediction.Transaction intents
422 transaction_intent_tampered
422 transaction_intent_tampered
serializedTransaction.422 transaction_intent_wrong_signer
422 transaction_intent_wrong_signer
expectedSigner. That field
is the wallet pinned to the API key.409 transaction_intent_expired
409 transaction_intent_expired
412 transaction_intent_version_conflict
412 transaction_intent_version_conflict
If-Match version was stale. Re-read the intent and submit with its
current version.I got 202 but do not know whether the transaction landed
I got 202 but do not know whether the transaction landed
202 means durably queued, not confirmed. Poll
blank.transactionIntents.get(intentId) honouring Retry-After until
status is confirmed, failed, or expired. See Operations and
transaction intents.Webhooks
422 webhook_url_disallowed
422 webhook_url_disallowed
.localhost, .local, .internal,
.home.arpa, or .onion are rejected. The policy is re-applied to every
redirect hop.Signature verification always fails
Signature verification always fails
Deliveries fail during a secret rotation
Deliveries fail during a secret rotation
v1 with the new
secret and v0 with the previous one. Pass both to verifyWebhookSignature
via secret and previousSecret until the window closes.Deliveries land in dead_letter
Deliveries land in dead_letter
responseStatus and lastErrorCode on the
delivery: http_<status> is your endpoint rejecting the request,
request_timeout means you exceeded the 10-second delivery timeout, and
url_disallowed or redirect_limit_exceeded are URL policy failures. Fix
the endpoint, then drain with blank.webhooks.replay(deliveryId).I received the same event twice
I received the same event twice
Blank-Event-Id, which is stable
across retries and replays; Blank-Delivery-Id changes each attempt.Deliveries time out under load
Deliveries time out under load
2xx as soon as the event is
durably written to a queue or table, and do the real work asynchronously.Still stuck
Collect therequestId, the code, and the operation ID, and report them together. Never share an API key, a webhook signing secret, or a signed transaction.