> ## Documentation Index
> Fetch the complete documentation index at: https://blank.build/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> The RFC 9457 Problem Details envelope used by Blank API v2, plus the stable asynchronous failure-code catalogues.

# Errors

Every Blank API v2 error is an [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) Problem Details document served as `application/problem+json`.

```json theme={null}
{
  "type": "https://blank.build/docs/reference/errors#validation_failed",
  "title": "Validation failed",
  "status": 422,
  "detail": "One or more request fields are invalid.",
  "instance": "/api/v2/predictions",
  "code": "validation_failed",
  "requestId": "req_0123456789abcdef0123456789abcdef",
  "errors": [
    {
      "path": "body.predictedPriceInSol",
      "code": "invalid_format",
      "message": "Expected a decimal string."
    }
  ]
}
```

| Field | Type | Always present | Meaning |
| - | - | - | - |
| `type` | URI | Yes | `https://blank.build/docs/reference/errors#{code}` |
| `title` | string | Yes | Short, stable, human-readable summary |
| `status` | integer | Yes | Matches the HTTP status |
| `detail` | string | Yes | Explanation of this occurrence |
| `instance` | string | Yes | The request path |
| `code` | string | Yes | **The stable machine-readable identifier — branch on this** |
| `requestId` | string | Yes | `req_` plus 32 hex characters, matching `X-Request-Id` |
| `errors` | array | No | Up to 100 field errors on validation failures |
| `context` | object | No | Safe, typed facts about this occurrence |
| `causes` | array | No | Other applicable failures discovered during the same check |

> [!WARNING]
>
> Branch on `code`, never on `detail` or `title`. `detail` is written for humans
> and may be reworded. `code` is part of the contract.

Error responses are always `Cache-Control: private, no-store`.

## Handling errors with the SDK

```ts theme={null}
import { BlankApiError, BlankNetworkError } from "@blankdotbuild/sdk";

try {
  await blank.predictions.create(input, { idempotencyKey });
} catch (error) {
  if (error instanceof BlankApiError) {
    console.error(error.code, error.status, error.requestId);
    console.error(error.fieldErrors); // problem.errors
    console.error(error.retryAfterSeconds);
    console.error(error.rateLimit); // { limit, remaining, resetAt, policy }
    console.error(error.idempotencyKey); // safe to retry with this key
    console.error(error.problem); // the full Problem Details document
  } else if (error instanceof BlankNetworkError) {
    // "request_aborted" | "request_timeout" | "network_error"
    console.error(error.code, error.idempotencyKey);
  }
  throw error;
}
```

`BlankNetworkError` means the outcome is **unknown**, not that the request failed. If it carries an `idempotencyKey`, retry the same request with that key rather than assuming anything.

The SDK also raises `BlankNetworkError` when Blank returns a success body that violates the operation's contract schema, so malformed data never reaches your code. Error responses are checked against the exact operation, HTTP status, code, canonical title, and type URI. If Blank or an intermediary returns anything else, the SDK synthesises `invalid_error_response` and uses the actual HTTP response status.

## Retry guidance by status

| Status | Retry? | Notes |
| - | - | - |
| 400, 401, 403, 404, 405, 409, 412, 413, 415, 422, 428 | No | Fix the request. `409 idempotency_request_in_progress` is the one exception — retry after `Retry-After` |
| 429 | Yes | Wait for `Retry-After` |
| 500 | Cautiously | Retry idempotent reads; retry mutations only with the same idempotency key |
| 502, 503, 504 | Yes | Transient; back off with jitter |

## Common codes

These can be returned by any operation.

| Status | Code | Meaning | Fix |
| - | - | - | - |
| 401 | `invalid_api_key` | Missing, malformed, unknown, expired, revoked, or wrong-environment key | Check the prefix and environment, then rotate |
| 403 | `insufficient_scope` | The key lacks a required scope | Create a new key with the scope; scopes cannot be widened |
| 404 | `not_found` | No route matched the request | Check the method and path against the [HTTP reference](https://blank.build/docs/reference/http-reference.md) |
| 405 | `method_not_allowed` | The path exists but not for this HTTP method | Use a method listed in the `Allow` response header |
| 422 | `validation_failed` | Request failed schema validation | Read `errors[].path` and `errors[].code` |
| 429 | `rate_limit_exceeded` | Rate policy exceeded | Honour `Retry-After` and the rate-limit headers |
| 500 | `internal_error` | Unexpected server failure | Retry if safe; report the `requestId` |
| 503 | `api_dependency_unavailable` | A backing dependency is unavailable | Retry with the same idempotency key |
| 503 | `rate_limit_unavailable` | The rate limiter could not be reached | Retryable; requests fail closed |

One further code is produced by the SDK rather than the API. If a response cannot be
parsed as a Problem Details document, the SDK synthesises one so your error handling
still receives a consistent shape:

| Status | Code | Meaning | Fix |
| - | - | - | - |
| Actual HTTP status | `invalid_error_response` | Blank returned an error body that is not valid Problem Details | Usually a proxy or gateway rewriting the response; report `requestId` |

## Mutation codes

Returned by any operation that writes.

| Status | Code | Meaning | Fix |
| - | - | - | - |
| 400 | `malformed_json` | Body is not valid JSON | Send well-formed JSON |
| 415 | `unsupported_content_type` | Missing or non-JSON `Content-Type` | Send `Content-Type: application/json` |
| 400 | `idempotency_key_invalid` | Key is missing or outside 8–128 printable ASCII characters | Send a valid `Idempotency-Key` |
| 409 | `idempotency_key_reused` | The same key was used with a different request | Use a new key, or resend the identical request |
| 409 | `idempotency_request_in_progress` | The first attempt is still running | Wait for `Retry-After: 1` and retry the same key |
| 412 | `precondition_failed` | `If-Match` did not match the resource version | Re-read the resource and retry |
| 413 | `request_body_too_large` | Body exceeds 1,048,576 bytes | Send a smaller body |
| 400 | `precondition_invalid` | `If-Match` is not one strong positive-integer ETag | Send the exact ETag returned by the latest read |
| 428 | `precondition_required` | A versioned mutation omitted `If-Match` | Read the resource, then send its current ETag |

## Pagination and caching

| Status | Code | Meaning | Fix |
| - | - | - | - |
| 400 | `cursor_invalid` | Cursor is malformed, tampered with, or does not match the current query | Restart the traversal without a cursor and keep the query stable |

## Tokens and market data

| Status | Code | Fix |
| - | - | - |
| 404 | `token_not_found` | Check the mint address; the token may not be publicly visible |
| 404 | `holder_stats_not_found` | No indexed holder snapshot exists yet |
| 422 | `market_time_range_invalid` | Send a valid `from`/`to` window for the requested interval |
| 503 | `token_index_unavailable` | Transient indexer unavailability; retry |
| 503 | `market_data_unavailable` | Transient market-data unavailability; retry |

## Identity, staking, fees, and presales

| Status | Code | Fix |
| - | - | - |
| 503 | `identity_unavailable` | Retry `GET /me` |
| 404 | `staking_pool_not_found` | Staking is not enabled for this token, or the pool is not indexed yet |
| 404 | `presale_not_found` | Check the presale ID |
| 404 | `presale_participation_not_found` | The key's pinned wallet has no participation in this presale |
| 503 | `manifest_unavailable` | Retry the Solana manifest read |

## Price predictions

Prediction eligibility evaluates every independent requirement, including finalized token balance even when the round is closed. The eligibility response can therefore contain both `round_not_open` and `insufficient_balance`. A submission returns one primary Problem Details code and places any other failures found during the same evaluation in `causes[]`; each item has its canonical `code`, `title`, `detail`, and optional `context`.

| Status | Code | Fix |
| - | - | - |
| 403 | `prediction_wallet_mismatch` | `walletAddress` must equal the key's pinned wallet |
| 403 | `prediction_authorization_invalid` | Sign the exact stored delegated intent with its wallet |
| 404 | `prediction_intent_not_found` | Check the intent ID and use the same integration API key |
| 404 | `prediction_round_not_found` | Check the mint and round ID |
| 404 | `prediction_not_found` | The prediction does not exist or belongs to another account |
| 404 / 409 | `prediction_token_not_enabled` | The token has no Price Prediction allocation; this is fixed at launch |
| 409 | `prediction_round_not_open` | Submit while the round is `open`, before `locksAt` |
| 409 | `prediction_wallet_not_verified` | Verify the wallet on the Blank account |
| 409 | `prediction_username_required` | Set a username on the Blank account |
| 409 | `prediction_insufficient_balance` | The wallet does not hold enough of the token at the evaluated slot |
| 409 | `prediction_already_submitted` | One prediction per wallet per round; predictions are immutable |
| 409 | `prediction_intent_expired` | Prepare and sign a new delegated intent while the round is open |
| 409 | `prediction_intent_consumed` | Replay with the original idempotency key or prepare a new intent |
| 409 | `prediction_exact_values_locked` | Standings are withheld until the round durably locks |
| 422 | `prediction_price_out_of_range` | Positive decimal, at most 12 integer and 18 fractional digits |
| 503 | `prediction_balance_unavailable` | Finalized balance data is temporarily unreadable; retry |

## Transaction-intent result codes

These codes appear in `TransactionIntent.error.code` after asynchronous Solana processing. They are not HTTP Problem Details codes.

| Code | Meaning | Next action |
| - | - | - |
| `solana_transaction_failed` | Solana reported that the submitted transaction failed | Prepare a fresh intent after correcting the cause |
| `transaction_intent_invalid_state` | Stored intent state could not be processed safely | Report the intent ID and request ID to Blank |
| `transaction_intent_expired` | Confirmation was not established before the intent expired | Prepare and sign a fresh intent |
| `transaction_intent_broadcast_exhausted` | Delivery retries ended before broadcast could complete | Read the intent, then prepare a fresh one |
| `solana_blockhash_expired` | The transaction blockhash expired before confirmation | Prepare and sign a fresh intent |
| `solana_signature_mismatch` | The RPC returned a signature different from the signed payload | Report the intent ID; do not resubmit blindly |

## Operations and transaction intents

| Status | Code | Fix |
| - | - | - |
| 404 | `operation_not_found` | Unknown operation, or owned by another account |
| 404 | `transaction_intent_not_found` | Unknown intent, or owned by another account |
| 409 | `transaction_intent_not_prepared` | The intent is no longer awaiting a signature |
| 409 | `transaction_intent_expired` | The blockhash window closed; request a fresh intent |
| 412 | `transaction_intent_version_conflict` | Re-read the intent and resubmit with its current version |
| 422 | `transaction_intent_tampered` | Sign the prepared message byte-for-byte |
| 422 | `transaction_intent_wrong_signer` | Sign with the wallet in `expectedSigner` |
| 422 | `transaction_intent_invalid_signature` | The signature failed verification; re-sign |

## Webhooks

| Status | Code | Fix |
| - | - | - |
| 404 | `webhook_endpoint_not_found` | Unknown endpoint, or owned by another account |
| 404 | `webhook_delivery_not_found` | Unknown delivery, or owned by another account |
| 409 | `webhook_delivery_not_replayable` | The delivery is not in a replayable state |
| 409 | `webhook_endpoint_limit_reached` | An account may hold at most 20 endpoints; delete one before creating another |
| 409 | `webhook_delivery_replay_limit_reached` | A delivery may be replayed at most 10 times |
| 422 | `webhook_url_disallowed` | Use a public HTTPS URL that satisfies the [endpoint URL policy](https://blank.build/docs/for-developers/webhooks.md) |

### Webhook delivery result codes

`WebhookDelivery.lastErrorCode` is one of the following transport codes, or `http_<status>` for an HTTP rejection such as `http_500`:

| Code | Meaning |
| - | - |
| `request_timeout` | The 10-second delivery deadline elapsed |
| `network_error` | The destination could not be reached |
| `url_disallowed` | URL or DNS policy rejected the destination |
| `redirect_limit_exceeded` | More than three redirects were returned |
| `redirect_location_missing` | A redirect response omitted `Location` |
| `http_<status>` | The destination returned a non-2xx HTTP code |

## Reporting a problem

Include the `requestId` and the `code`. They identify the exact request in Blank's telemetry. Never paste an API key, a webhook signing secret, or a signed transaction into a report.

For symptom-first guidance, see [Troubleshooting](https://blank.build/docs/reference/troubleshooting.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.