---
title: "Blockchain SQL Query"
method: POST
path: "/gateway/v1/onchain/sql"
tags: ["Onchain"]
---

# Blockchain SQL Query

`POST /gateway/v1/onchain/sql`

Run a raw ClickHouse `SELECT` query against blockchain data.

All tables live in the **agent** database. Discover tables and columns first with `GET /v1/onchain/schema`.

Send a JSON object, not raw SQL text. Example: `{"sql":"SELECT ...","max_rows":1000}`.

Execution modes
- The default response is synchronous and has a 30s timeout.
- For queries that may exceed that window, send `Prefer: respond-async`. The same request is accepted as a durable job with `202 Accepted` and `Preference-Applied: respond-async`.
- Send a stable `Idempotency-Key` with `respond-async` so equivalent client retries reuse the retained job.
- A synchronous timeout is non-retryable and returns `recommended_action: use_async_job` plus the async job URL. Do not resubmit the synchronous request automatically.
- Synchronous and asynchronous ClickHouse executions share one cluster-wide per-owner concurrency budget. Accepted async jobs remain queued while that owner's execution slots are occupied.

Rules
- Only SELECT/WITH statements allowed (read-only)
- All table references must be database-qualified: `agent.<table_name>`
- Max 10,000 rows (default 1,000), 30s timeout
- **Always filter on block_date or block_number** — partition key, without it queries will timeout
- Normalize address literals to lowercase and compare stored address columns directly. Wrapping address columns in `lower()` is rejected because it prevents key/index pruning.
- On transfer tables, do not combine `from` and `to` address predicates with OR. Use separate `UNION ALL` branches so each branch can use its address-ordered projection, and account for self-transfers when preserving counts.
- **Never resolve token symbols here** — `symbol` columns are unindexed (full scan) and symbol matches surface scam clones. Use `GET /v1/search/token?q={symbol}&chain={chain}` to get the contract address, then filter by `contract_address`
- For transfer tables, `amount` is decimal-adjusted display units; `amount_raw` is the original base-unit value
- Avoid `SELECT *` on large tables — specify only the columns you need
- Use single quotes for ClickHouse strings. Example: `toDate('2026-04-07')`.

Data refresh: ~24 hours.

Example

```json
{
  "sql": "SELECT block_time, token_pair, amount_usd, taker, tx_hash FROM agent.ethereum_dex_trades WHERE block_date >= today() - 7 AND project = 'uniswap' AND amount_usd > 100000 ORDER BY amount_usd DESC LIMIT 20",
  "max_rows": 1000
}
```

## Headers

- `Prefer` string — Optional RFC 7240 preference. Include respond-async to submit this request as a durable asynchronous job and receive 202 Accepted.
- `Idempotency-Key` string — Stable client-generated key used when Prefer includes respond-async. Reusing it returns the existing retained job.

## Request body

- HumaOnchainSQLInputBody
  - `$schema` string, uri — A URL to the JSON Schema for this object.
  - `max_rows` integer — Maximum number of rows to return
  - `sql` string, required — Send a JSON object with the sql field. Example: {"sql":"SELECT 1"}. Use GET /v1/onchain/schema for tables and columns. Transfer table amount is decimal-adjusted; amount_raw is the original base-unit value. Use single quotes for ClickHouse strings. Example: toDate('2026-04-07').

## Response `200`

Synchronous SQL result

- DataResponseOnchainRow
  - `$schema` string, uri — A URL to the JSON Schema for this object.
  - `data` object[], nullable, required
  - `meta` OffsetMeta, required
    - `cached` boolean, required — Whether this response was served from cache
    - `credits_used` integer, required — Credits deducted for this request
    - `empty_reason` string — Hint explaining why the data array is empty, when applicable
    - `has_more` boolean — Whether more items may exist beyond this response. For offset-paged endpoints, continue with a larger offset. For time-series endpoints without offset/cursor controls, true means the requested time range hit an upstream cap; narrow from/to to continue. Omitted when exhaustion cannot be proven.
    - `limit` integer, required — Maximum number of items returned in this response
    - `offset` integer, required — Number of items skipped (pagination offset)
    - `total` integer — Total number of matching items (before pagination). Omitted when total is unknown.
    - `watermark` integer — Warehouse watermark (Unix seconds) this response was computed at, on warehouse-backed endpoints (e.g. Hyperliquid /trades/aggregate) — rows up to this time come from the warehouse, newer rows from the live tail. Omitted elsewhere.

## Other responses

- `202` — Durable asynchronous SQL job accepted
- `408` — Synchronous query ended; use the structured action guidance
- `default` — Error

---

[API](https://skmtc.dev/asksurf/apis/asksurf-public-rest-api.md) · [All operations](https://skmtc.dev/asksurf/apis/asksurf-public-rest-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/asksurf/asksurf-public-rest-api/revisions/498f461e81c4/schema)
