◈   ⌘ api · Intermediate

Dune Query API with Python: On-Chain Data for Traders

Learn how to use the Dune Analytics Query API with Python to pull on-chain blockchain data, automate analysis, and build smarter crypto trading strategies.

Uncle Solieditor · voc · 06.05.2026 ·views 29
◈   Contents
  1. → What Is the Dune Query API and Why It Matters
  2. → Setting Up API Access and Authentication
  3. → Executing Queries and Fetching Results
  4. → Practical Use Cases: On-Chain Signals for Real Trading
  5. → Parameterized Queries and Advanced Patterns
  6. → Frequently Asked Questions
  7. → Putting It All Together

On-chain data is one of the most underused edges in crypto trading. While retail traders are glued to price charts on Binance and Bybit, the real signal often lives in the raw blockchain — wallet flows, liquidity shifts, smart contract activity. Dune Analytics makes that data accessible through SQL queries, and their API lets you pull it directly into Python for automation, alerting, and strategy building.

The Dune Query API gives you programmatic access to any query saved on Dune's platform — including thousands of community-built dashboards tracking DEX volumes, whale wallets, stablecoin flows, and more. Pair that with Python and you have a lightweight but powerful data pipeline that most traders aren't using.

What Is the Dune Query API and Why It Matters

Dune Analytics is a platform where analysts write SQL queries against indexed blockchain data — Ethereum, Base, Solana, Arbitrum, Polygon, and more. Instead of syncing your own node or paying for expensive data providers, you write SQL and Dune handles the infrastructure.

The Query API extends this by letting you execute queries and retrieve results programmatically. This matters because manual dashboard checks don't scale. If you're watching 20 tokens, checking whale accumulation patterns, or monitoring liquidity on OKX or Coinbase's on-chain activity, you need automation — not a browser tab.

Dune's free tier allows a limited number of API calls per month and query executions may be throttled. For production pipelines, the Premium plan gives you faster execution and higher rate limits. Always cache results locally to avoid burning your quota on repeated identical calls.

Setting Up API Access and Authentication

Getting started requires a Dune account and an API key. Head to your Dune profile settings and generate an API key under the API section. Keep this key in an environment variable — never hardcode it in scripts you might share or push to GitHub.

pip install requests pandas python-dotenv

Create a `.env` file in your project root and store your key there. Then load it in your Python script using dotenv. Here's the base setup that every Dune API script should start with:

import os
import requests
import pandas as pd
from dotenv import load_dotenv
from time import sleep

load_dotenv()

API_KEY = os.getenv("DUNE_API_KEY")
BASE_URL = "https://api.dune.com/api/v1"

HEADERS = {
    "X-Dune-API-Key": API_KEY,
    "Content-Type": "application/json"
}

def get(endpoint: str, params: dict = None) -> dict:
    url = f"{BASE_URL}{endpoint}"
    response = requests.get(url, headers=HEADERS, params=params)
    response.raise_for_status()
    return response.json()

def post(endpoint: str, payload: dict = None) -> dict:
    url = f"{BASE_URL}{endpoint}"
    response = requests.post(url, headers=HEADERS, json=payload or {})
    response.raise_for_status()
    return response.json()

Executing Queries and Fetching Results

The Dune API works in two steps: first you trigger a query execution, then you poll for results. Queries don't always run instantly — especially complex ones joining multiple blockchain tables. The execution model is async by design, so your code needs to handle the wait.

Every Dune query has a numeric ID visible in its URL — for example, `dune.com/queries/3141592` has query ID `3141592`. You'll use this ID to trigger runs and fetch results. Here's a complete function that handles the full execute-poll-return cycle:

def execute_query(query_id: int, parameters: dict = None) -> pd.DataFrame:
    """
    Execute a Dune query and return results as a DataFrame.
    Handles async polling with exponential backoff.
    """
    # Trigger execution
    payload = {}
    if parameters:
        payload["query_parameters"] = parameters

    exec_response = post(f"/query/{query_id}/execute", payload)
    execution_id = exec_response["execution_id"]
    print(f"Execution started: {execution_id}")

    # Poll for completion
    wait = 2
    max_attempts = 30

    for attempt in range(max_attempts):
        status_response = get(f"/execution/{execution_id}/status")
        state = status_response["state"]
        print(f"Attempt {attempt + 1}: state = {state}")

        if state == "QUERY_STATE_COMPLETED":
            break
        elif state in ("QUERY_STATE_FAILED", "QUERY_STATE_CANCELLED"):
            raise RuntimeError(f"Query {query_id} failed with state: {state}")

        sleep(min(wait, 30))  # cap wait at 30s
        wait *= 1.5
    else:
        raise TimeoutError(f"Query {query_id} did not complete after {max_attempts} attempts")

    # Fetch results
    result_response = get(f"/execution/{execution_id}/results")
    rows = result_response["result"]["rows"]
    columns = result_response["result"]["metadata"]["column_names"]

    return pd.DataFrame(rows, columns=columns)


# Example: fetch latest ETH whale transfers (replace with any public query ID)
df = execute_query(query_id=3141592)
print(df.head())
Instead of re-executing a query every time, use the `/query/{id}/results` endpoint to fetch the latest cached results without burning an execution. This is much faster and costs fewer API credits — only trigger a fresh execution when you need truly up-to-date data.

Practical Use Cases: On-Chain Signals for Real Trading

Raw data fetching is just the start. The real value is connecting on-chain signals to your trading workflow. Here are patterns that traders actually use in production.

One of the most powerful use cases is tracking net exchange inflows and outflows. When large amounts of BTC or ETH move to Binance, selling pressure typically follows. When assets leave exchanges to cold wallets, it signals accumulation. You can build a Dune query for this and poll it hourly via Python.

VoiceOfChain already surfaces derived trading signals from on-chain activity in real time — but for traders who want to build custom signals around specific wallets, protocols, or tokens, the Dune API is the right tool. You can cross-reference VoiceOfChain signals with your own Dune data to validate before entering a position on Bybit or OKX.

import time
import smtplib
from email.message import EmailMessage

def fetch_latest_results(query_id: int) -> pd.DataFrame:
    """Fetch cached results without triggering a new execution."""
    response = get(f"/query/{query_id}/results")
    rows = response["result"]["rows"]
    columns = response["result"]["metadata"]["column_names"]
    return pd.DataFrame(rows, columns=columns)


def monitor_exchange_inflows(query_id: int, threshold_usd: float = 5_000_000):
    """
    Poll exchange inflow query every 5 minutes.
    Alert if net inflow exceeds threshold (bearish signal).
    """
    print(f"Monitoring exchange inflows (alert at >${threshold_usd:,.0f})...")

    while True:
        try:
            df = fetch_latest_results(query_id)

            # Assume query returns columns: symbol, net_inflow_usd
            high_inflow = df[df["net_inflow_usd"] > threshold_usd]

            if not high_inflow.empty:
                for _, row in high_inflow.iterrows():
                    print(f"ALERT: {row['symbol']} net inflow = ${row['net_inflow_usd']:,.0f}")
                    # Add your notification logic here (Telegram, email, webhook)

        except requests.HTTPError as e:
            print(f"API error: {e}")
        except Exception as e:
            print(f"Unexpected error: {e}")

        time.sleep(300)  # poll every 5 minutes


# Replace with your actual query ID tracking exchange inflows
monitor_exchange_inflows(query_id=1234567, threshold_usd=5_000_000)

Parameterized Queries and Advanced Patterns

One underused feature of the Dune API is parameterized queries. Instead of hardcoding a wallet address or token contract in your SQL, you define parameters and pass them at execution time via the API. This turns a single Dune query into a reusable function.

For example, you could have a single query that analyzes any ERC-20 token's holder distribution, and call it with different contract addresses as you screen tokens. This is especially useful for traders on platforms like Gate.io or KuCoin that list new tokens frequently — you can run due diligence on holder concentration before entering a position.

def analyze_token_holders(query_id: int, token_address: str, min_balance_usd: float = 1000) -> pd.DataFrame:
    """
    Run a parameterized Dune query for a specific token.
    The Dune query must define {{token_address}} and {{min_balance_usd}} as parameters.
    """
    parameters = {
        "token_address": token_address,
        "min_balance_usd": str(min_balance_usd)
    }

    df = execute_query(query_id=query_id, parameters=parameters)

    if df.empty:
        print(f"No holder data found for {token_address}")
        return df

    total_holders = len(df)
    top10_pct = df.nlargest(10, "balance_usd")["balance_usd"].sum() / df["balance_usd"].sum() * 100

    print(f"Token: {token_address}")
    print(f"Holders above ${min_balance_usd:,.0f}: {total_holders}")
    print(f"Top 10 holders control: {top10_pct:.1f}% of supply")

    if top10_pct > 80:
        print("WARNING: Highly concentrated — potential rug risk")
    elif top10_pct < 40:
        print("INFO: Well distributed supply")

    return df


# Example: screen a new token before buying on KuCoin or Gate.io
df = analyze_token_holders(
    query_id=9876543,  # your parameterized holder query
    token_address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",  # USDC as example
    min_balance_usd=5000
)
Dune API Endpoints Reference
EndpointMethodPurpose
/query/{id}/executePOSTTrigger a new query execution
/execution/{id}/statusGETCheck execution state
/execution/{id}/resultsGETFetch results by execution ID
/query/{id}/resultsGETFetch latest cached results
/query/{id}GETGet query metadata and SQL

Frequently Asked Questions

Do I need a paid Dune plan to use the Query API?
The API is available on free plans with limited monthly credits. For production use — especially if you're running frequent executions or need low latency — the Premium plan is worth the cost. Free tier is fine for experimentation and low-frequency polling.
How fresh is the data returned by the Dune API?
Data freshness depends on when the query was last executed. Dune indexes blockchain data with a short delay (usually minutes behind tip for major chains like Ethereum). When you fetch cached results, you get whatever the last execution returned — trigger a fresh execution if you need current data.
Can I use the Dune API to track specific wallet addresses?
Yes. Write a Dune SQL query filtering by wallet address, save it on Dune, then call it via the API with the wallet as a parameter. This is useful for tracking known whale wallets, team wallets, or protocol treasuries before making moves on Binance or Coinbase.
What blockchains does Dune support?
Dune indexes Ethereum, Arbitrum, Optimism, Base, Polygon, BNB Chain, Solana, Avalanche, and more. Coverage varies by chain maturity on the platform. Check Dune's documentation for the current list and available decoded tables for each chain.
Is there a Python SDK for Dune Analytics?
There is an unofficial community SDK called `dune-client` on PyPI that wraps the API. It works well for simple use cases, but building directly on the REST API (as shown in this guide) gives you more control over polling behavior, error handling, and caching logic.
How do I avoid hitting Dune API rate limits?
Cache results locally using pickle or SQLite after each fetch. Use the `/query/{id}/results` endpoint (cached) instead of triggering fresh executions unless you need real-time data. For monitoring loops, schedule executions during off-peak hours and poll cached results frequently.

Putting It All Together

The Dune Query API bridges the gap between raw blockchain data and actionable trading intelligence. With a few hundred lines of Python you can build monitoring systems that catch whale movements before they show up in price action, screen new token listings for concentration risk before buying on Binance or Bitget, and automate the kind of on-chain due diligence that used to take hours of manual dashboard work.

The pattern is consistent regardless of what you're tracking: find or write a Dune query, save it, grab the query ID, and call it from Python on whatever schedule makes sense for your strategy. Start simple — poll one query, log the results, check for anomalies. Add complexity only when the simple version proves its value.

For traders who want on-chain signals without building their own pipeline, VoiceOfChain delivers pre-processed signals in real time — covering market structure, sentiment shifts, and volume anomalies across major pairs. The Dune API and platforms like VoiceOfChain are complementary: one gives you raw data flexibility, the other gives you speed. The best-informed traders use both.

Start with public community queries before writing your own SQL. Dune's discovery page has thousands of battle-tested queries tracking DEX volumes, stablecoin flows, NFT activity, and more. Fork one, call it via the API, and iterate from there — it's much faster than starting from a blank SQL editor.
◈   more on this topic
◉ basics Mastering the ccxt library documentation for crypto traders ⌂ exchanges Mastering the Binance CCXT Library for Crypto Traders ⌬ bots Best Crypto Trading Bots 2025: Profitable AI-Powered Strategies