Appearance
Errors
Every error carries a stable machine-readable code plus a human-readable message. Codes never change meaning; messages may be reworded. Both official SDKs surface the code (Error::Api { code, .. } in Rust, BlinkApiError.code in TypeScript).
REST
Errors return a JSON envelope with an HTTP status:
json
{ "code": "NOT_FOUND", "error": "market 99999 not found" }| Code | Status | Meaning |
|---|---|---|
INVALID_PARAMETER | 400 | A parameter is missing or invalid (interval, market_type, status, empty q, malformed cursor). The message names the parameter and the accepted values. |
NOT_FOUND | 404 | Resource does not exist: unknown market, account, subaccount, token, client order id, or transaction hash. |
INTERNAL_ERROR | 500 | Internal error. Safe to retry with backoff. |
Successful responses are always 200 with the documented shape; list endpoints return [] rather than 404 when the account or market simply has no data yet.
WebSocket
Protocol errors arrive as error frames on the open connection:
json
{ "type": "error", "code": "UNKNOWN_CHANNEL", "error": "unknown channel `bogus`" }| Code | Meaning | Connection |
|---|---|---|
INVALID_MESSAGE | Frame was not valid protocol JSON. | stays open |
UNKNOWN_CHANNEL | Subscription named a channel that does not exist. | stays open |
INVALID_SUBSCRIPTION | Missing a required field (market_id, interval, address/subaccount_id), or sets a field the channel's messages never carry — e.g. is_perps on mark_price (matching is exact per set field, so an inapplicable field would otherwise silently match nothing). | stays open |
CONNECTION_LAGGED | The connection fell behind and buffered messages were dropped; deltas cannot be replayed. | closed — reconnect and re-subscribe (see lag handling) |
FEED_UNAVAILABLE | This deployment does not serve the streaming feed. | closed |
Transaction submission
Two distinct failure surfaces exist, and only one of them is on-chain:
1. Rejected at admission (the common case). The sequencer pre-executes every transaction before sequencing it. A transaction that would fail — insufficient margin, bad nonce, invalid signature, an order outside the price band — is rejected synchronously and never reaches a block. The submission response carries the failure, e.g.:
Transaction rejected: Transaction execution unsuccessfulRejected transactions do not consume a nonce and are not indexed; GET /transactions/{hash} returns 404 for them. Retrying after fixing the cause is safe.
2. Sequenced but reverted (rare). Transactions that bypass sequencer admission — forced inclusion via the data-availability layer — execute in a block and can revert there. These ARE indexed, with status: "failed" and the receipt's reason in error:
json
{ "hash": "0x…", "status": "failed", "error": "insufficient margin",
"block": "1346", "success": false, "timestamp_ms": 1717000000000 }So in the transaction resource, committed and failed both mean sequenced; admission rejections surface only in the submit response.
Nonce races
Concurrent submissions from one account can lose a nonce race and come back as an admission rejection. Because rejected transactions never sequence, resubmitting with the same payload is safe and is what the official clients do.