Skip to main content
The client libraries for TypeScript, Python and Rust do everything on this page for you and return JSON rows. Read on if you want Arrow tables (for Parquet, pandas, Polars, DuckDB) or use another language.
POST /query returns application/vnd.apache.arrow.stream. Any Apache Arrow library can read it. There is one thing to get right: the body is several Arrow IPC streams back to back, one per row type, not one stream.

The response layout

  • One complete Arrow IPC stream per row type you asked for, always in the order transactions, then logs, then blocks (whatever order you listed them in).
  • Each stream has its own schema, its record batches, and its own end-of-stream marker.
  • Every stream’s schema carries the same bl.* metadata. bl.streams lists the streams in order ("transactions,logs") and bl.row_type names the current one.
  • The first schema arrives as soon as the query is planned, before any rows.
A correct reader:
  1. Opens a stream reader on the body and reads it to its end-of-stream marker.
  2. Reads bl.streams from its schema, then opens the next stream on the same body, until it has read that many.
  3. Checks that the total rows received equal bl.objects.
Check for truncation. If a query fails after the server has started sending, the server can’t send an error body any more: it closes the connection. Most Arrow readers raise an error when a body ends mid-batch, but a body cut exactly between two batches can look like a valid, shorter stream. bl.objects is in the schema, sent before any row, and equals the number of rows the response carries. Compare it with what you received, and treat a mismatch as a failed request. The readers below do this.

Complete readers

Each of these is a small, complete client that returns Arrow tables: it sends the query, decodes every stream, checks for truncation and reports API errors with their code. Copy one into your project.
Needs pyarrow and requests (pip install pyarrow requests). query() also retries rate limits, busy servers and broken connections; see Errors and retries.
bloklake_arrow.py
In any other language, the recipe is the same: open an Arrow IPC stream reader on the response body, read it to the end, read the next one from the same position, repeat bl.streams times, then compare the row count with bl.objects. Go’s ipc.NewReader, Java’s ArrowStreamReader and C++‘s RecordBatchStreamReader all work this way.

Into a dataframe

Arrow tables convert to dataframes without copying the data.
See Data types for how hashes, addresses and 128/256-bit amounts are encoded, with helpers to convert them.

Reading incrementally

The readers above hold one response in memory at a time. A response carries about 8 MB of row data at most, so that’s fine for almost everyone, and the pagination loop keeps memory flat across a whole backfill. If you want to process rows while a response is still arriving (lower latency to the first row), iterate the record batches instead of collecting them (for batch in reader in pyarrow, for await (const batch of reader) in arrow-js). Keep reading promptly: a response that isn’t read for 60 seconds, or is read slower than 50 KB/s, is cut off.