All posts

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 requests and pandas
  • A PumpFunData API key (connect your wallet and generate one)
pip install requests pandas pyarrow

API overview

The API has two endpoints. Both require an API key passed via the X-API-Key header.

EndpointPurposeCost
/rangeCheck available date range and file countFree
/downloadDownload one hourly Parquet file1 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 range
resp = 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 range
resp_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.parquet

In 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 os
import 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 os
import requests
from 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 = START
while 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 pd
import glob
# Load all hours for one day
files = 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, and token_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 time
token = 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.

StatusMeaningWhat to do
400Invalid exchange name or missing parametersCheck that exchange is pump_fun or pump_amm
401Missing or invalid API keyCheck the X-API-Key header
404No data for that date/hourUse /range to check available dates
429Rate limitedWait 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 os
import requests
import pandas as pd
import glob
API_KEY = "pfd_your_key_here"
BASE = "https://api.pumpfundata.com"
EXCHANGE = "pump_fun"
DATE = "2026-04-15"
# 1. Check available dates
range_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 day
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"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 summarize
files = 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 swaps
swaps = 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_fun and pump_amm data on token_mint to follow tokens from bonding curve through AMM trading.
  • Analyze creators. Use the token_creator field 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.