May 8, 2026
Pump.fun API Guide: Download Historical Trading Data with Python
PumpFunData provides a REST API for downloading Pump.fun and Pump.fun AMM historical data as hourly Parquet files. Every swap, token creation, graduation, and liquidity event is included. This guide walks through authentication, querying available dates, downloading files, and working with the data in Python.
Prerequisites
- Python 3.10+ with
requestsandpandas - A PumpFunData API key (connect your wallet and generate one)
pip install requests pandas pyarrowAPI overview
The API has two endpoints. Both require an API key passed via the X-API-Key header.
| Endpoint | Purpose | Cost |
|---|---|---|
| /range | Check available date range and file count | Free |
| /download | Download one hourly Parquet file | 1 credit |
Both endpoints take an exchange parameter. Use pump_fun for bonding curve data (swaps, token creations, graduations) or pump_amm for the graduated AMM (swaps, liquidity events). The rate limit is 30 requests per minute.
Step 1: Check available dates
Before downloading, call /range to see what dates are available and how many hourly files exist. This endpoint is free and does not cost any credits.
curl -H "X-API-Key: pfd_your_key_here" \ "https://api.pumpfundata.com/range?exchange=pump_fun"The response tells you the first and last available date, plus the total number of hourly files.
{ "exchange": "pump_fun", "start": "2026-02-08", "end": "2026-05-07", "files": 2110}In Python:
import requests
API_KEY = "pfd_your_key_here"BASE = "https://api.pumpfundata.com"
# Check pump_fun date rangeresp = requests.get( f"{BASE}/range", params={"exchange": "pump_fun"}, headers={"X-API-Key": API_KEY},)data = resp.json()print(f"Available: {data['start']} to {data['end']} ({data['files']} hourly files)")
# Check pump_amm date rangeresp_amm = requests.get( f"{BASE}/range", params={"exchange": "pump_amm"}, headers={"X-API-Key": API_KEY},)print(resp_amm.json())Step 2: Download a single file
The /download endpoint takes three parameters: exchange, date (YYYY-MM-DD), and hour (00-23). It streams back a Parquet file. Each download costs 1 credit.
curl -H "X-API-Key: pfd_your_key_here" \ "https://api.pumpfundata.com/download?exchange=pump_fun&date=2026-04-15&hour=12" \ -o pump_fun_2026-04-15_12.parquetIn Python:
resp = requests.get( f"{BASE}/download", params={"exchange": "pump_fun", "date": "2026-04-15", "hour": "12"}, headers={"X-API-Key": API_KEY},)
if resp.status_code == 200: with open("pump_fun_2026-04-15_12.parquet", "wb") as f: f.write(resp.content) print("Downloaded successfully")else: print(f"Error: {resp.json()}")If the file does not exist (for example, a future date or a rare gap), the API returns a 404 with a message pointing you to /range to check available dates.
Step 3: Download a full day
A full day is 24 hourly files and costs 24 credits. Loop over hours 0-23 and save each file.
import osimport requests
API_KEY = "pfd_your_key_here"BASE = "https://api.pumpfundata.com"DATE = "2026-04-15"EXCHANGE = "pump_fun"OUT_DIR = f"data/{EXCHANGE}/{DATE}"os.makedirs(OUT_DIR, exist_ok=True)
for hour in range(24): resp = requests.get( f"{BASE}/download", params={"exchange": EXCHANGE, "date": DATE, "hour": str(hour)}, headers={"X-API-Key": API_KEY}, ) if resp.status_code == 200: path = f"{OUT_DIR}/{EXCHANGE}_{DATE}_{hour:02d}.parquet" with open(path, "wb") as f: f.write(resp.content) print(f"Downloaded hour {hour:02d}") else: print(f"Hour {hour:02d}: {resp.status_code}")To download AMM data for the same day, change EXCHANGE to "pump_amm". The API, file format, and credit cost are all the same.
Step 4: Download a date range
For backtesting or analysis you usually want multiple days. Here is a script that downloads a full week. A week of data is 168 credits (7 days × 24 hours).
import osimport requestsfrom datetime import datetime, timedelta
API_KEY = "pfd_your_key_here"BASE = "https://api.pumpfundata.com"START = datetime(2026, 4, 10)END = datetime(2026, 4, 16)EXCHANGE = "pump_fun"
current = STARTwhile current <= END: date_str = current.strftime("%Y-%m-%d") out_dir = f"data/{EXCHANGE}/{date_str}" os.makedirs(out_dir, exist_ok=True)
for hour in range(24): resp = requests.get( f"{BASE}/download", params={"exchange": EXCHANGE, "date": date_str, "hour": str(hour)}, headers={"X-API-Key": API_KEY}, ) if resp.status_code == 200: path = f"{out_dir}/{EXCHANGE}_{date_str}_{hour:02d}.parquet" with open(path, "wb") as f: f.write(resp.content)
print(f"Done: {date_str}") current += timedelta(days=1)Step 5: Load and explore the data
Each Parquet file has a flat schema. Every row is one on-chain event with an event_typefield that tells you what happened. Fields that don't apply to a given event type are null.
import pandas as pdimport glob
# Load all hours for one dayfiles = sorted(glob.glob("data/pump_fun/2026-04-15/*.parquet"))df = pd.concat([pd.read_parquet(f) for f in files], ignore_index=True)
print(f"Total events: {len(df):,}")print(f"Event types:\n{df['event_type'].value_counts()}")print(f"Unique tokens: {df['token_mint'].nunique():,}")print(f"Unique wallets: {df[df['event_type'] == 'swap']['user_wallet'].nunique():,}")For pump_fun data you will see three event types:
- swap — a buy or sell on the bonding curve. Includes
action,token_amount,lamports_amount,fee_lamports, and reserve snapshots after the trade. - create — a new token launch. Has
name,symbol,uri, andtoken_total_supply. - bonding_complete — the token graduated and moved to the AMM.
For pump_amm data you get swap, create (pool creation), and liquidity (deposit and withdraw) events. The AMM uses real_lamports_reserve and real_token_reserve instead of virtual reserves.
Step 6: Compute token prices
Every swap event includes the pool reserves after the trade. For pump_fun data, the spot price is the ratio of virtual reserves. For pump_amm data, use the real reserves instead.
LAMPORTS_PER_SOL = 1_000_000_000
# Bonding curve price (pump_fun)swaps = df[df["event_type"] == "swap"].copy()swaps["price_sol"] = ( swaps["virtual_lamports_reserve"] / swaps["virtual_token_reserve"] / LAMPORTS_PER_SOL)swaps["time"] = pd.to_datetime(swaps["timestamp"], unit="s")
# Pick a token and see its price over timetoken = swaps["token_mint"].value_counts().index[0]token_swaps = swaps[swaps["token_mint"] == token].sort_values("timestamp")print(f"Token: {token}")print(f"Swaps: {len(token_swaps)}")print(f"Price range: {token_swaps['price_sol'].min():.10f} — {token_swaps['price_sol'].max():.10f} SOL")For a deeper explanation of virtual reserves and the bonding curve formula, see Pump.fun Bonding Curve Explained.
Error handling
The API returns standard HTTP status codes with JSON error messages.
| Status | Meaning | What to do |
|---|---|---|
| 400 | Invalid exchange name or missing parameters | Check that exchange is pump_fun or pump_amm |
| 401 | Missing or invalid API key | Check the X-API-Key header |
| 404 | No data for that date/hour | Use /range to check available dates |
| 429 | Rate limited | Wait and retry (limit is 30 req/min) |
Full working script
Here is a complete script that checks the date range, downloads a day of data, and prints a summary. Copy it and replace the API key with yours.
import osimport requestsimport pandas as pdimport glob
API_KEY = "pfd_your_key_here"BASE = "https://api.pumpfundata.com"EXCHANGE = "pump_fun"DATE = "2026-04-15"
# 1. Check available datesrange_resp = requests.get( f"{BASE}/range", params={"exchange": EXCHANGE}, headers={"X-API-Key": API_KEY},)range_data = range_resp.json()print(f"Available: {range_data['start']} to {range_data['end']} ({range_data['files']} files)")
# 2. Download all hours for the dayout_dir = f"data/{EXCHANGE}/{DATE}"os.makedirs(out_dir, exist_ok=True)
for hour in range(24): resp = requests.get( f"{BASE}/download", params={"exchange": EXCHANGE, "date": DATE, "hour": str(hour)}, headers={"X-API-Key": API_KEY}, ) if resp.status_code == 200: path = f"{out_dir}/{EXCHANGE}_{DATE}_{hour:02d}.parquet" with open(path, "wb") as f: f.write(resp.content) print(f"Hour {hour:02d} ✓") elif resp.status_code == 429: print(f"Rate limited at hour {hour:02d}, wait and retry") else: print(f"Hour {hour:02d}: {resp.status_code}")
# 3. Load and summarizefiles = sorted(glob.glob(f"{out_dir}/*.parquet"))df = pd.concat([pd.read_parquet(f) for f in files], ignore_index=True)
print(f"\nTotal events: {len(df):,}")print(f"Event types: {df['event_type'].value_counts().to_dict()}")print(f"Unique tokens: {df['token_mint'].nunique():,}")
# 4. Compute prices for swapsswaps = df[df["event_type"] == "swap"].copy()swaps["price_sol"] = ( swaps["virtual_lamports_reserve"] / swaps["virtual_token_reserve"] / 1_000_000_000)print(f"Swaps: {len(swaps):,}")print(f"Unique traders: {swaps['user_wallet'].nunique():,}")Next steps
- Backtest strategies. Use the downloaded data to test trading ideas. See our backtesting guide for a complete example.
- Track graduations. Join
pump_funandpump_ammdata ontoken_mintto follow tokens from bonding curve through AMM trading. - Analyze creators. Use the
token_creatorfield to find wallets that repeatedly launch successful tokens. - Compare exchanges. Read Pump.fun vs PumpSwap AMM to understand the differences in safety and data schema.
Ready to start downloading?
PumpFunData has every Pump.fun and Pump.fun AMM event since February 2026, in hourly Parquet files.