For AI agents: the documentation index is at /llms.txt. Markdown versions of pages are available by appending .md to the URL.
Skip to main content

Query & Response

A query selects a slot range, optional filters on instruction calls / transactions / logs / account activity, and the columns you want. The server returns matched rows plus a next_slot cursor. Some slots have no block, so blocks can be sparse across the requested range.

Query shape​

{
"from_slot": 391800000,
"to_slot": 391800100,
"include_all_blocks": false,
"instruction_calls": [ ... ],
"transactions": [ ... ],
"logs": [ ... ],
"account_activity": [ ... ],
"field_selection": { ... }
}
  • from_slot is inclusive, to_slot is exclusive. Omit to_slot to run toward the current head.
  • Within one selection object, all set fields are AND-ed. Multiple objects within one array are OR-ed. Different arrays are AND-ed against each other (see the warning below).
  • With filters, you get blocks only for slots where something matched. Set include_all_blocks: true, or use no filters, to get every block in the range. See All-blocks mode.
A second selection array narrows the result, it does not widen it

Selections in different arrays are intersected. Adding a transactions filter next to an instruction_calls filter returns only the instructions whose transaction also matches; a selection that matches nothing zeroes every table in the response, with no error or warning.

Measured on slot 437500000:

QueryInstruction rows
instruction_calls: [{executing_account: [Tokenkeg...], d1: ["03"]}] alone44
transactions: [{fee_payer: [P]}] alone, where P paid for one of those transactions16
both together2
the instruction filter plus transactions: [{fee_payer: ["1111...1111"]}] (matches nothing)0

For a union, send separate queries and merge. This is about which rows match; whether a matched row's related rows come back is decided by field_selection (see Join behavior).

Unknown top-level keys are rejected

The top-level query object and field_selection are strict: an unrecognized key (a removed table such as balances / token_balances, the removed include_account_activity flag, or a misspelled max_num_*) fails the whole query rather than silently widening it. Keys inside a selection object are still ignored if unknown. To request every activity row in a range, use an empty selection: "account_activity": [{}].

A rejected query, like any malformed value (a bad base58 pubkey, an unknown log kind), comes back as HTTP 422 with a plain-text body naming the offending key, for example Failed to deserialize the JSON body into the target type: balances: unknown field `balances`, expected one of ....

Several keys were renamed; legacy names are still accepted on input, but responses use the new names. See Renamed fields and compatibility.

Filters​

InstructionSelection​

Selects instruction calls (a single program invocation, including inner CPIs). Array key: instruction_calls (legacy: instructions).

FieldDescription
executing_accountMatch the invoked program (base58 pubkeys). Legacy: program_id.
d1 / d2 / d4 / d8First N bytes of instruction data as hex; 0x prefix optional ("0x03" and "03" are equivalent).
a0 - a9Account pubkey at that index in the instruction's account metas (a0 = first). Which index is "the mint", "the pool", etc. is defined by the program's IDL, not by Solana globally.
is_innertrue = inner only, false = outer only, omitted = both.
tx_successtrue = instructions of successful transactions only, false = failed only, omitted = both. Legacy: is_committed.

tx_success is the success of the parent transaction, applied to every instruction call of that transaction (Solana metadata only records instructions that actually ran). Set "tx_success": true to drop failed transactions server-side. "tx_success": false can legitimately match nothing: servers running the failed-transaction trim keep no instruction rows for failed transactions; the transactions themselves are still served (with err and fee) via the transactions table's success filter.

TransactionSelection​

FieldDescription
fee_payerMatch fee payer pubkey.
transaction_idMatch by signatures[0] (base58), the canonical Solana transaction signature.
transaction_indexMatch by transaction_index. See transaction_index semantics.
successtrue = succeeded only, false = failed only, omitted = both.

LogSelection​

FieldDescription
program_idMatch log emitter program.
kindParsed log line category (below).
kindTypical meaning
invokeProgram <id> invoke <depth>
successProgram <id> success
failedProgram <id> failed: ...
consumedProgram <id> consumed <n> of <m> compute units
logProgram log: ...
dataProgram data: <base64>
otherAnything else (full text still in message)

An unknown kind in a filter is an error; on the response side an unrecognized value decodes as other. Not every range carries the invoke / success / failed / consumed lines: SQD-ingested and default RPC-ingested ranges only carry log / data / other, so do not assume every invocation has an invoke row.

AccountActivitySelection​

Selects rows of the unified account_activity table (native SOL and SPL token movements). An empty selection {} matches every activity row in range.

FieldDescription
kind"native" (the rows the old balances table held), "token" (the old token_balances rows), or omitted for both.
accountMatch by account address: the token account on a token row, the wallet on a native row.
transaction_idMatch by the transaction's base58 signatures[0].
mintMatch by mint. Only token rows carry a mint, so this restricts to token activity.
ownerMatch by owner (wallet). The stored column is split into pre_owner / post_owner; this filter matches either side, so an in-transaction SetAuthority(AccountOwner) change still matches.
program_idMatch by token program id (SPL Token vs Token-2022), pre or post.
is_signer / is_writable / is_fee_payer / from_lookup_tableHeader-derived position flags. A null flag matches neither true nor false.

Because account means different things on the two sides, "everything for wallet W" is two selections: [{ "account": ["W"] }, { "owner": ["W"] }].

Field selection​

field_selection chooses columns per table, and also decides which related tables come back:

  • For a table you filter on (a non-empty selection array such as instruction_calls), an omitted or empty field list returns all its columns.
  • A table you don't filter on comes back only when you list at least one of its fields. The one exception is blocks in all-blocks mode.
{
"field_selection": {
"block": ["slot", "blockhash", "block_time"],
"instruction_call": ["slot", "executing_account", "data", "d8"],
"transaction": ["slot", "fee_payer", "success"]
}
}

Available fields (by table)​

TableFields
blockslot, blockhash, parent_slot, parent_blockhash, block_time, block_height
transactionslot, transaction_index, transaction_id, signatures, fee_payer, success, err, fee, compute_units_consumed, account_keys, recent_blockhash, version, loaded_addresses_writable, loaded_addresses_readonly, has_dropped_log_messages
instruction_callslot, transaction_index, instruction_address, executing_account, executing_account_index, account_arguments, account_index_arguments, data, d1, d2, d4, d8, a0-a9, is_inner, tx_success, error, compute_units_consumed
logslot, transaction_index, instruction_address, program_id, kind, message
account_activityslot, transaction_index, transaction_id, account_index, account, pre_balance, post_balance, is_signer, is_writable, is_fee_payer, from_lookup_table, mint, pre_owner, post_owner, token_decimals, pre_token_balance, post_token_balance, pre_program_id, post_program_id, token_state
rewardslot, pubkey, lamports, post_balance, reward_type, commission

Field notes:

  • tx_success (instruction call): see InstructionSelection. Legacy column name: is_committed.
  • instruction_address (instruction call, log): where the instruction sits in the transaction: [2] = third top-level instruction; [2, 0] = first inner instruction inside it.
  • data / d1-d8 (instruction call): hex-encoded instruction bytes, without a 0x prefix in responses even if your filter used one.
  • executing_account_index / account_index_arguments (instruction call): positions of the executing account and account arguments in the transaction's resolved key list (account_keys ++ ALT writable ++ ALT readonly). Null when the source could not resolve positions.
  • error / compute_units_consumed (instruction call): per-invocation failure reason (e.g. "custom program error: 0x1") and compute units. SQD serves both directly; RPC and Firehose ranges derive them from the Program <id> failed / consumed log lines, so both are null where the source did not record them.
  • has_dropped_log_messages (transaction): true when the validator truncated this transaction's logs, so its logs rows are incomplete. Null means the source could not say.
  • transaction_id (transaction) and token_state (account activity) are computed at serving time but selected like any other field. token_state is not_a_token, opened (token account created in this transaction), closed, or persisted; select it instead of inferring "is this a token row" from null mint.

Value types in responses​

  • Every response field is optional. A missing value means "not selected, or the source could not supply it", never zero or false.
  • Addresses, hashes and signatures are base58 strings, parsed strictly (32 bytes for a pubkey/blockhash, 64 for a signature). A malformed value in a filter is an error, not a filter that silently matches nothing.
  • Token balances are strings. pre_token_balance / post_token_balance are raw base units (scale by token_decimals) as decimal strings, so JavaScript consumers don't lose precision above 2^53. Raw SPL amounts are u64 on-chain in both SPL Token and Token-2022. Lamport fields (pre_balance / post_balance, fee) stay numeric.

transaction_index semantics​

transaction_index is a dense 0..n rank over the stored, non-vote transactions of a slot, in block order. It is not the transaction's original position in the block: vote transactions are excluded at ingest, and every source is renumbered onto the same key. (slot, transaction_index) is the join key tying instruction_calls, logs, and account_activity rows to their transaction; do not compare it against an index from an RPC getBlock response.

The account_activity table​

account_activity replaces the old balance and token_balance tables. Each row is one account's activity in one transaction:

  • Native side (pre_balance, post_balance, lamports): populated when the account's SOL balance changed, null otherwise.
  • Token side (mint, pre_owner, post_owner, token_decimals, pre_token_balance, post_token_balance, pre_program_id, post_program_id): populated when the account appears in the transaction's token-balance metadata, null otherwise.

A row commonly carries both sides, since a token account also holds lamports; the two are independent axes (for wrapped SOL, lamports equal the token amount plus the rent-exempt reserve). A null pre_owner means the token account was opened in this transaction, a null post_owner that it was closed (same convention for pre_program_id / post_program_id). account_index is the account's position in the transaction's resolved key list; the flags (is_signer, is_writable, is_fee_payer, from_lookup_table) come from the message header and are null where a source could not supply them.

Join behavior​

The server joins related rows based on which tables you include in field_selection. transactions, instruction_calls, logs and account_activity share the key (slot, transaction_index); blocks and rewards join on slot.

  • A transaction qualifies when it matches every table you filter on.
  • Each filtered table returns its matched rows of qualifying transactions.
  • Each table you select in field_selection but don't filter returns all its rows for qualifying transactions. Filter on instruction_calls and select transaction fields, and you get the parent transaction of each matched instruction; filter on transactions and select instruction_call fields, and you get every instruction, outer and inner, of each matched transaction.
  • blocks and rewards come back for the slots of qualifying transactions when you select their fields.

There is one join mode and no join_mode parameter. Tell us on Discord or GitHub if you need a different shape.

All-blocks mode​

With include_all_blocks: true, or with no filters at all, you get every block in the range, including slots where nothing matched. Blocks come back even if you select no block fields, and then with all their columns, so list the fields you need.

Rewards and account activity are not added automatically. Select their fields and they come back for the whole range, not only for matching transactions. If you also filter on account_activity, you get only the matching rows, as usual. Whole-range rewards and activity are large, so keep the range small.

Limits (optional)​

Approximate server-side caps on rows returned per table: max_num_blocks, max_num_transactions, max_num_instructions (this key keeps the legacy noun; max_num_instruction_calls is rejected), max_num_logs, max_num_account_activity. When you set none, the server applies its own default caps, which are usually fine. Caps cut a response at a whole-slot boundary, so a single slot larger than the cap still comes back in full.

Response​

Top-level keys: next_slot, total_execution_time_ms, optional rollback_guard, and one key per table when present: blocks, transactions, instruction_calls, logs, account_activity, rewards.

Each table is an array of row batches, not a flat array of rows

instruction_calls is [[row, row, ...], ...], the same framing EVM HyperSync uses for data. A single response is usually one batch, so table[0] looks like it works until it hands you a batch instead of a row. Flatten first: in jq, [.instruction_calls[][]], and the row count is [.instruction_calls[][]] | length. Batches carry no meaning of their own (they do not correspond to blocks or transactions).

{
"next_slot": 391800050,
"total_execution_time_ms": 12,
"rollback_guard": null,
"blocks": [[{ "slot": 391800000, "blockhash": "8dK...", "block_time": 1731000123 }]],
"instruction_calls": [[{
"slot": 391800000,
"executing_account": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"account_arguments": ["7xK...", "9mY...", "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
"data": "33e685a4017f83ad2e0f27c68d080000c61cf79a00000000",
"tx_success": true
}]],
"transactions": []
}

Instruction data is the raw instruction bytes as hex; for filtering, use the discriminator fields (d1 / d2 / d4 / d8). On failure, transaction.err is the chain's error structure as a JSON-encoded string (e.g. "{\"InstructionError\":[2,{\"Custom\":2}]}"), so parse it before inspecting it.

Pagination​

Use the response's next_slot as the next request's from_slot.

  • Bounded scan (to_slot set): repeat while next_slot < to_slot.
  • To head (no to_slot): repeat while next_slot strictly increases. If it doesn't, you've caught up to the head or hit a limit; back off.

The server may stop before to_slot on a row cap, a time budget or a size budget. Each response still covers [from_slot, next_slot) completely for every table and holds nothing at or past next_slot, so paging this way never skips or repeats a row.

Reorg detection (rollback_guard)​

rollback_guard describes the server's current in-memory head window, not the slots in the response: slot_number / blockhash are the last slot in the window and its hash, first_slot_number / first_previous_blockhash the first slot in the window and its parent hash. It is present on most responses, including ones far behind head, and null when the server has no complete window to describe. The shape mirrors the EVM RollbackGuard with Solana naming.

{
"slot_number": 391800099,
"timestamp": 1731000000,
"blockhash": "8dK...",
"first_slot_number": 391800000,
"first_previous_blockhash": "3nF..."
}

Use it to detect a shallow reorg before committing near-head data: if you have already ingested first_slot_number - 1 or slot_number, compare the blockhashes you stored for them against first_previous_blockhash / blockhash. A mismatch means what you ingested is no longer on the server's head fork; re-sync from a finalized slot or the newest slot whose hash still agrees. The window moves between calls, so do not compare one page's guard against the next page's, and never treat slot numbers alone as stable identifiers near head; reconcile with blockhash / parent_blockhash.

Renamed fields and compatibility​

Legacy names are still accepted on input, but responses use the new names.

LocationLegacy nameCurrent name
Top-level queryinstructionsinstruction_calls
InstructionSelectionprogram_idexecuting_account
InstructionSelectionis_committedtx_success
field_selectioninstructioninstruction_call
instruction_call fieldprogram_idexecuting_account
instruction_call fieldaccountsaccount_arguments
instruction_call fieldis_committedtx_success

Removed, not renamed (each is a loud error because the envelope rejects unknown keys):

  • The balance / token_balance field-selection tables and the top-level balances / token_balances selections: use account_activity.
  • The top-level include_account_activity flag: use "account_activity": [{}].
  • The account_activity.owner column, split into pre_owner / post_owner. The owner filter is unchanged and matches either side.

Authentication​

Every endpoint requires a Bearer token except GET /height and GET /height/sse. See API tokens for how to generate one.