API Conventions
Every operation in API v2 follows the same transport contract. Learn it once and it applies everywhere.Base URL and versioning
X-Blank-Api-Version: 2. Paths outside /api/v2 are internal Blank planes with no stability contract — do not call them.
Request headers
JSON request bodies are capped at 1,048,576 bytes. A larger body returns
413 request_body_too_large; an unparseable body returns 400 malformed_json.
Version-guarded mutations return 428 precondition_required when If-Match is absent, 400 precondition_invalid when it is not one strong positive-integer ETag, and 412 precondition_failed when a well-formed ETag is stale.
Response headers
The SDK surfaces all of this on
result.metadata:
Pagination and cursors
List operations return a cursor page:cursor— opaque, signed, and bound to the query that produced it. Never construct, decode, or mutate one. Changing filters mid-traversal invalidates the cursor.limit— integer from 1 to 100, default 50.- Stop when
hasMoreisfalseornextCursorisnull. - A malformed, tampered, or mismatched cursor returns
400 cursor_invalid.
maxPages defaults to 100 and is capped at 10,000. The iterator throws rather than looping forever once the bound is reached, so pick a value that matches the dataset you expect.
Two list responses are not cursor paginated because they are hard-capped: the staking leaderboard and prediction standings each return at most 100 entries as { data: [...] }.
Rate limits
Every operation is assigned a named rate policy, reported inX-RateLimit-Policy:
public-read-cheap, public-read-expensive, api-key-read, operation-read, prediction-submit, transaction-prepare, transaction-submit, webhook-admin, export
Each policy is enforced across several dimensions at once. Anonymous requests are limited globally and per client IP. Authenticated requests are additionally limited per API key, per account, and per wallet. The response headers always describe the tightest dimension currently applying to you.
Exceeding a limit returns 429 rate_limit_exceeded with Retry-After and the rate-limit headers. If the limiter itself cannot be reached, requests fail closed with 503 rate_limit_unavailable, which is retryable.
Caching and conditional requests
Anonymous reads are cacheable and carry anETag. Send it back on the next request:
304 Not Modified with no body and does not consume bandwidth. Cache lifetimes vary by operation, from max-age=2 for market snapshots and trades up to max-age=300 for the Solana manifest.
Authenticated reads are always private, no-store, and so is every error response. Never put them in a shared cache.
Some authenticated resources still return an ETag — but it is the resource’s integer version, used with If-Match for optimistic concurrency, not a caching hint.
Idempotency
Every mutation requires anIdempotency-Key header. There are no exceptions.
- Format: 8 to 128 printable ASCII characters.
- Retention: 24 hours per key, scoped to the API key identity, HTTP method, and route.
- The claim, the state transition, the stored response, and the outbox record commit atomically in Postgres, so a replay returns exactly what the first attempt returned.
BlankApiError and BlankNetworkError both carry idempotencyKey, so an uncertain mutation can always be retried safely with the same key.
Optimistic concurrency
Resources that can be updated concurrently carry an integerversion. Guarded mutations require a strong If-Match header quoting that version:
412 precondition_failed for webhook endpoints and 412 transaction_intent_version_conflict for transaction intents. Re-read the resource, re-apply your change, and retry.
Retries and deadlines
The SDK retries at most twice, with full-jitter backoff, for:- network failures and timeouts
429,502,503, and504
Retry-After is surfaced to you instead of being slept through.
Deadlines are per attempt, not per call:
timeoutMs— per-attempt deadline. Default 30,000; valid range 100 to 120,000.retries—0,1, or2. Defaults to2, and can be set per client or per call.signal— anAbortSignalthat cancels the call and any pending backoff.
4xx except 429 or 409 idempotency_request_in_progress; both carry Retry-After.
Numeric precision
Prices, SOL amounts, and other decimal quantities are canonical decimal strings such as"0.00001234". Raw on-chain amounts and lamport values are unsigned integer strings such as "1000000000".
They are strings on purpose: IEEE 754 doubles cannot represent them exactly. Parse them with BigInt or a decimal library, never with Number.
Response validation
The SDK validates every success response against the generated operation schema before returning it. It also validates every error’s HTTP status, code, canonical title, and type URI against that operation. An invalid success raisesBlankNetworkError; an invalid error becomes BlankApiError with invalid_error_response. This is why the SDK version should track the API contract — see the OpenAPI reference.
Errors
Every error is RFC 9457application/problem+json. See Errors for the envelope and the full code catalogue.