# About us & media kit
Source: https://docs.dexpaprika.com/about
Learn what DexPaprika does, our connection to CoinPaprika, and download our media kit with logos and icons.
## What we do
DexPaprika gives you clear, reliable DEX data, live and historical, so you can build with confidence. We bring together on-chain markets across major networks to power dashboards, trading systems, research, and AI agents.
* We're a team based in Poznań, Poland, with years of crypto data experience
* Our API gives fast access to tokens, pools, and DEX activity
* Developer-first docs, SDKs, and examples
* Consistent schema across supported blockchains
## Backed by CoinPaprika
DexPaprika is built by the team behind CoinPaprika, a crypto data platform trusted since 2018. We started DexPaprika to offer a lightweight, purpose-built layer for DEX markets, backed by CoinPaprika’s reliability and uptime.
* Our parent company: [CoinPaprika](https://coinpaprika.com)
## Media kit
Download our branding kit with logos and icons for press and integration use.
* **Media pack**: [Download media\_pack.zip](https://dexpaprika.com/media_pack_dexpaprika.zip)
### FAQs
Yes. You can call it keyless at 50,000 credits a month, and a free registered key raises that to 300,000. Paid plans are on [pricing](https://dexpaprika.com/api/pricing).
DexPaprika is built by the CoinPaprika team to focus specifically on on‑chain DEX markets.
Use the media pack above; it contains logos and icons for press/integrations.
# Brand assets
Source: https://docs.dexpaprika.com/ai-integration/brand-assets
DexPaprika logos, colors, and the downloadable brand kit. Everything you need to use the DexPaprika mark correctly in an article, app, listing, or integration.
Use these when you write about, integrate, or list DexPaprika. The whole kit
(logos, symbol, app icons, social banner, and usage rules) is one download, and
every logo is also linked individually below for direct use.
Logos (color / white / black), the symbol in four variants, favicon and app
icons, a social banner, and the full README rulebook. SVG + PDF + PNG.
## Colors
| Role | HEX | RGB |
| ---------------- | --------- | ------------- |
| DexPaprika Green | `#00FF75` | 0, 255, 117 |
| White | `#FFFFFF` | 255, 255, 255 |
| Ink (logo black) | `#1C1C1C` | 28, 28, 28 |
| Dark surface | `#252425` | 37, 36, 37 |
Green is the brand. Use it for the pepper, the PAPRIKA wordmark, accents, and
CTAs. It is made for dark backgrounds; on light backgrounds pair it with ink.
## Which file do I use
| Your background | Logo | Symbol |
| -------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Dark | [logo-color](/images/brand/dexpaprika-logo-color.svg) (primary) | [symbol-color-on-dark](/images/brand/dexpaprika-symbol-color-on-dark.svg) |
| Light / white | [logo-black](/images/brand/dexpaprika-logo-black.svg) | [symbol-color-on-light](/images/brand/dexpaprika-symbol-color-on-light.svg) |
| Single-color (dark) | [logo-white](/images/brand/dexpaprika-logo-white.svg) | [symbol-white](/images/brand/dexpaprika-symbol-white.svg) |
| Single-color (light) | [logo-black](/images/brand/dexpaprika-logo-black.svg) | [symbol-black](/images/brand/dexpaprika-symbol-black.svg) |
The primary logo is `dexpaprika-logo-color` (white DEX + green PAPRIKA), used on
dark backgrounds by default. SVG is the master; use it wherever you can.
## Logo and symbol
The symbol plus the DEXPAPRIKA wordmark. The default is the color logo (white DEX, green PAPRIKA), shown here on its dark surface. For light backgrounds use [logo-black](/images/brand/dexpaprika-logo-black.svg).
The pepper-in-brackets mark alone, for avatars, app icons, and tight spaces.
## Clear space and minimum size
* **Clear space:** keep empty space around the logo equal to the height of one
bracket corner (roughly the cap-height of the DEXPAPRIKA letters).
* **Minimum size:** full logo no smaller than 120px wide on screen (25mm print);
symbol no smaller than 24px. For anything smaller, use the app icons in the kit.
## Do and don't
**Do** use the SVG, match the logo to the background, and keep clear space.
**Don't** recolor the green, stretch, rotate, add effects, place the logo on a
busy or low-contrast background, separate the pepper from its brackets, or
re-typeset the wordmark. There is no separate brand typeface: the wordmark is
vector artwork inside the logo files.
## Using the DexPaprika name and logo
DexPaprika provides free DEX and on-chain data across 36 chains. You are welcome
to use these marks to link to or credit DexPaprika as a data source. Please do
not imply partnership or endorsement without permission. Questions: contact our
Lead Developer Advocate Mateusz Sroka at [msroka@coinpaprika.com](mailto:msroka@coinpaprika.com).
# Connect DexPaprika to ChatGPT
Source: https://docs.dexpaprika.com/ai-integration/chatgpt-actions
Give ChatGPT live DEX market data from DexPaprika. Add it as a custom GPT Action (works on any paid plan, keyless) or as a native MCP connector in ChatGPT Developer Mode (beta).
There are two ways to give ChatGPT live DexPaprika data. Start with **GPT Actions**: it works on any paid ChatGPT plan, needs no API key, and is generally available. The **native MCP connector** is a second option, but it is a beta feature behind ChatGPT Developer Mode.
## Option 1: Custom GPT with an Action (recommended)
A custom GPT with a DexPaprika Action queries networks, pools, tokens, and search from inside a ChatGPT conversation. Keyless, no beta.
Ready-to-use OpenAPI schema: [`https://mcp.dexpaprika.com/openapi`](https://mcp.dexpaprika.com/openapi). It exposes **12 keyless GET operations** (networks, pools, DEXes, pool details, OHLCV, transactions, token details, top tokens, batch prices, search, stats) with no authentication.
**Prerequisites:** a paid ChatGPT plan (Plus, Pro, Team, or Enterprise). Custom GPTs are built on the web at [chatgpt.com](https://chatgpt.com); you cannot build one from the mobile app.
On the web, open **Explore GPTs**, click **Create**, then open the **Configure** tab. Give it a name (for example "DexPaprika Crypto Assistant") and an instruction like:
```
You are a cryptocurrency and DeFi data assistant powered by DexPaprika. You can
query blockchain networks, DEXes, liquidity pools, token prices, OHLCV history,
and cross-chain search. Always call the action for live data rather than
guessing, and include metrics like liquidity, 24h volume, and price when
relevant.
```
Scroll to **Actions** and click **Create new action**. In the **Schema** field, choose **Import from URL** and paste:
```
https://mcp.dexpaprika.com/openapi
```
The builder lists the operations by `operationId`.
DexPaprika is keyless and read-only, so set **Authentication** to **None**. Optionally add a privacy policy URL ([dexpaprika.com/privacy-policy](https://dexpaprika.com/privacy-policy)) if the builder asks for one.
Click **Test** on an operation, then try a prompt like "What are the top 5 liquidity pools on Ethereum by volume?". When it returns live data, **Save** the GPT and choose its visibility.
Building GPTs is web-only, and some ChatGPT mobile builds have had trouble firing Actions. If an Action does not respond on mobile, test it on desktop web.
***
## Option 2: Native MCP connector (Developer Mode, beta)
ChatGPT can connect directly to our hosted MCP server as a custom connector. This exposes all 17 DexPaprika tools, but it is a **beta feature** and the setup UI changes between releases.
As of 2026-07, custom MCP connectors live in **ChatGPT Developer Mode (beta)** and require a **paid plan** (Plus, Pro, Business/Team, Enterprise, or Edu). Free ChatGPT cannot add custom connectors. On workspace plans, an admin may need to enable Developer Mode first. The exact menu labels move between releases, so treat the steps below as a guide and confirm the current labels in-product.
1. Open **Settings → Connectors**, find the **Advanced** area, and enable **Developer mode**.
2. In the Connectors panel, choose **Create / Add custom connector**.
3. Paste the DexPaprika MCP server URL: `https://mcp.dexpaprika.com/sse`. If your Developer Mode build expects a Streamable HTTP endpoint instead, use `https://mcp.dexpaprika.com/streamable-http`. (Do not use `/mcp`.)
4. Set **Authentication** to **No authentication**, accept the trust confirmation, and save.
5. The DexPaprika tools then appear in chat.
This is a general chat connector, not a Deep Research or Company Knowledge connector. Those require an MCP server to implement `search` and `fetch` tools in OpenAI's document-retrieval shape; DexPaprika has a `search` tool but no `fetch`, so it works through Developer Mode rather than those modes.
***
## Example prompts
Once connected either way, try:
* "Which blockchain networks does DexPaprika support?"
* "What are the most liquid USDC/ETH pools across all networks?"
* "What's the current price of SOL, and which DEX has the deepest liquidity for it?"
* "Find newly created pools on Base with high trading volume"
* "Compare 24h volume on PancakeSwap versus Uniswap"
* "Get 30-day OHLCV for the USDC/ETH pool on Uniswap V3"
***
## Troubleshooting
Confirm the schema URL is exactly `https://mcp.dexpaprika.com/openapi`, that your plan supports custom GPTs (any paid plan), and that authentication is set to None. Recreate the Action if the import failed midway.
Build and test on desktop web. Some ChatGPT mobile builds have not reliably triggered Actions.
It is a paid-plan beta and Free cannot add custom connectors. On workspace plans, an admin enables Developer Mode first. The toggle has moved between releases; look under Settings, Connectors, then an Advanced area.
## FAQs
Use a custom GPT with an Action. It works on any paid plan, needs no API key, and is not a beta feature. Use the native MCP connector only if you are in ChatGPT Developer Mode and want all 17 tools directly in chat.
No. Both paths are keyless. The OpenAPI schema and the MCP server are public and read-only. A free registered key raises the monthly allowance from 50,000 credits to 300,000, and Pro raises it to 5,000,000 at 300 requests a minute; see [rate limits](/knowledge-base/rate-limits).
Not as-is. Deep Research connectors require `search` and `fetch` tools in OpenAI's document-retrieval schema. DexPaprika has `search` but no `fetch`, so it connects through Developer Mode instead.
# DexPaprika Claude Code plugin - Quick setup guide
Source: https://docs.dexpaprika.com/ai-integration/claude-code-plugin-guide
Install the DexPaprika Claude Code plugin with two simple commands. Access real-time DEX data, liquidity pools, and token prices directly in Claude Code. Integrated MCP server with zero additional setup required.
## Tutorial overview
Get instant access to real-time DeFi data in Claude Code. The DexPaprika Claude Code plugin provides built-in access to decentralized exchanges, liquidity pools, and token pricing, all with just two commands.
The DexPaprika plugin is a **Claude Code plugin** with built-in MCP server access. Two commands. Zero additional setup. Real-time DEX data ready to use.
***
## What is the DexPaprika Claude Code plugin?
The DexPaprika plugin is a native Claude Code plugin that gives you instant access to real-time decentralized exchange (DEX) data, liquidity pool analytics, and token pricing information across 36 blockchain networks.
**Key features:**
* Real-time DEX and pool data access
* Token pricing and market analytics
* Liquidity pool information
* Coverage across 36 blockchain networks
* Built-in MCP server (no separate installation needed)
* Works in Claude Desktop and Cursor IDE
* Keyless to start: query without an API key, and [register a free key](https://console.dexpaprika.com) later to raise the monthly allowance from 50,000 credits to 300,000
***
## Quick installation (2 commands)
### Step 1: Add the CoinPaprika marketplace
```bash theme={null}
/plugin marketplace add coinpaprika/claude-marketplace
```
This adds access to the CoinPaprika marketplace where the DexPaprika plugin is hosted.
### Step 2: Install the DexPaprika plugin
```bash theme={null}
/plugin install dexpaprika
```
Select **"Install now"** when prompted.
The plugin bundles the MCP server, so there are no config files, environment variables, or API keys to set up. You now have real-time DeFi data across 36 networks in Claude Code.
***
## Using the plugin
Once installed, you can ask Claude about DeFi data naturally:
### Example queries
**Get pool information:**
```
What are the top liquidity pools on Ethereum by volume?
```
**Find token prices:**
```
Show me the current price of USDC and USDT on Ethereum.
```
**Analyze specific pools:**
```
Tell me about the USDC/ETH pool on Uniswap V3.
```
**Discover networks:**
```
What blockchain networks are supported by DexPaprika?
```
**Search for tokens:**
```
Find all pools that include the PEPE token.
```
Claude will automatically use the plugin to fetch real-time data and provide you with current market information.
***
## What's included in the plugin
The DexPaprika plugin gives Claude access to:
### Network information
* List of supported blockchain networks
* Network metadata and specifications
### Decentralized exchanges (DEXes)
* Available DEXes on each network
* DEX volume and activity data
* Protocol information
### Liquidity pools
* Top pools by volume, price, transactions
* Detailed pool metrics and token pairs
* Pool fee structures
* Recent transaction history
### Token data
* Current token prices in USD
* Market data and liquidity information
* Trading volume (5m, 15m, 1h, 6h, 24h)
* Token metadata and explorer links
### Search functionality
* Find tokens, pools, and DEXes across networks
* Search by name, symbol, or address
***
## Verify installation
After installing the plugin, you can check that it's working:
### Check available commands
```
/help
```
You should see the DexPaprika plugin listed among available commands.
### Test with a simple query
```
What networks does DexPaprika support?
```
Claude should return a list of supported blockchain networks (Ethereum, Solana, Base, Arbitrum, Polygon, etc.).
***
## Managing the plugin
### View installed plugins
```
/plugin
```
Select "Manage Plugins" to see all installed plugins and their status.
### Enable/disable the plugin
```
/plugin disable dexpaprika@coinpaprika
```
Disable the plugin if you want to reduce context usage, but keep it installed.
```
/plugin enable dexpaprika@coinpaprika
```
Re-enable the plugin when you need it again.
### Uninstall the plugin
```
/plugin uninstall dexpaprika@coinpaprika
```
Completely remove the plugin if you no longer need it.
### Update the plugin
```
/plugin marketplace update coinpaprika/claude-marketplace
```
Keep the plugin up-to-date with the latest features and improvements.
***
## Practical use cases
### Portfolio monitoring
Ask Claude to track your tokens:
```
Monitor these tokens on Ethereum:
- WETH (0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2)
- USDC (0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48)
- DAI (0x6b175474e89094c44da98b954eedeac495271d0f)
Show their current prices and highest volume pools.
```
### DEX comparison
```
Compare trading volumes on Uniswap and Curve across Ethereum and Polygon.
Which DEX has higher volume? Which pools are most active?
```
### Market research
```
On Solana, what are the top 10 liquidity pools by 24h volume?
For each pool, show token pairs, current price, volume in USD, and liquidity.
```
### Token discovery
```
Find emerging tokens on Base network.
Show the ones created recently with highest trading volume.
```
***
## How it works
The DexPaprika Claude Code plugin:
1. **Installs via the marketplace** - `/plugin install dexpaprika`
2. **Includes built-in MCP server access** - No separate MCP configuration needed
3. **Connects to DexPaprika API** - Fetches real-time DEX and token data
4. **Provides data to Claude** - Claude uses the data to answer your questions
5. **Returns formatted results** - Get market analysis, pool data, pricing information
The MCP server is **built into the plugin**, so you don't need to install or configure it separately.
***
## Troubleshooting
### Plugin not showing up
**Problem:** After installing, you don't see the DexPaprika plugin in `/help`
**Solution:**
1. Run `/plugin marketplace update coinpaprika/claude-marketplace`
2. Restart Claude Code
3. Run `/plugin install dexpaprika` again
### Claude not using the plugin
**Problem:** You ask about DEX data but Claude doesn't use the plugin
**Solution:**
* Ask explicitly: "Using DexPaprika, show me..."
* Or phrase questions about pools, tokens, and DEXes specifically
* Claude automatically recognizes plugin-relevant queries
### Plugin commands not working
**Problem:** `/plugin` commands not recognized
**Solution:**
* Ensure you're in Claude Code (not another terminal)
* Check that you're using the correct syntax: `/plugin install dexpaprika`
* Verify marketplace is added: `/plugin marketplace add coinpaprika/claude-marketplace`
### No data returned
**Problem:** Claude can't find data for your query
**Solution:**
* Check the network name (use lowercase: `ethereum`, `solana`, not `Ethereum`)
* Verify token address format for your blockchain
* Some tokens may not have pricing data; try with major tokens (WETH, USDC, DAI)
***
## Integration with CoinPaprika plugin
The same marketplace also hosts the **CoinPaprika plugin** for global cryptocurrency market data.
**Install both for complete crypto coverage:**
```bash theme={null}
/plugin install dexpaprika
/plugin install coinpaprika
```
**What you get:**
* **DexPaprika plugin** - DEX pools, on-chain liquidity, tokens on specific networks
* **CoinPaprika plugin** - Global crypto rankings, market cap, exchange data
Use them together to compare DEX prices with exchange prices and find arbitrage opportunities.
***
## Next steps
Explore the complete DexPaprika API documentation for all available data.
Learn how to efficiently fetch prices for multiple tokens.
Step-by-step guide to querying token data via the API.
Learn more about Claude Code plugins and marketplaces.
***
## Get support
Connect with our community and get real-time support.
Share your experience and help us improve the plugin.
### FAQs
Two commands:
1. `/plugin marketplace add coinpaprika/claude-marketplace`
2. `/plugin install dexpaprika`
No configuration files or environment setup needed.
No. Public read access is keyless, so you can start querying without authentication.
Yes. The plugin works in both Claude Desktop and Cursor IDE.
The **Claude Code plugin** is the easy way to get started, just install and use. It includes built-in access to the MCP server. If you need advanced MCP configuration, you can set up the MCP server separately for more control, but the plugin handles this for you automatically.
Yes. Run `/plugin disable dexpaprika@coinpaprika` to turn it off, then `/plugin enable dexpaprika@coinpaprika` to turn it back on.
The plugin receives updates automatically from the marketplace. Run `/plugin marketplace update coinpaprika/claude-marketplace` to check for updates.
36+ networks including Ethereum, Solana, Base, Arbitrum, Polygon, Optimism, Fantom, Avalanche, Binance Smart Chain, and more. Ask Claude: "What networks are supported?"
Yes! Install both for comprehensive crypto data:
* `DexPaprika` - DEX and on-chain pool data
* `CoinPaprika` - Global market cap and exchange data
Ask explicitly: "Using DexPaprika, show me..." or phrase your question about pools, tokens, and DEXes specifically.
The plugin only makes read-only API calls to public DexPaprika endpoints. No private data is transmitted. All queries are logged per standard Anthropic policy.
# Connect DexPaprika MCP to Codex
Source: https://docs.dexpaprika.com/ai-integration/codex
Add the DexPaprika MCP server to OpenAI Codex CLI with one command. Give Codex live DEX market data across 36 blockchains over streamable HTTP. No API key needed to start.
[OpenAI Codex CLI](https://github.com/openai/codex) supports MCP servers, so you can give it live DEX market data from DexPaprika. The hosted server is keyless, so there is no API key or bearer token to configure.
## Add it with one command
```bash theme={null}
codex mcp add dexpaprika --url https://mcp.dexpaprika.com/streamable-http
```
This writes the server to `~/.codex/config.toml`. Codex connects to it over streamable HTTP on the next run.
## Or configure it manually
Add this to `~/.codex/config.toml`:
```toml theme={null}
[mcp_servers.dexpaprika]
url = "https://mcp.dexpaprika.com/streamable-http"
```
No `bearer_token_env_var` or OAuth options are needed. The DexPaprika API is keyless.
## Verify
Start Codex and ask a question that needs live data:
```
Which blockchain networks does DexPaprika support, and what are the top pools on Base by volume?
```
Codex should call the DexPaprika tools and return current data. List configured servers any time with:
```bash theme={null}
codex mcp list
```
## What you get
The server exposes 17 tools covering networks, pools, tokens, OHLCV, transactions, and cross-chain search. See the [MCP tools reference](/ai-integration/mcp-tools) for the full list, parameters, and the `getCapabilities` onboarding tool.
## Resources
* [Hosted MCP server](/ai-integration/hosted-mcp-server)
* [MCP tools reference](/ai-integration/mcp-tools)
* [Codex CLI](https://github.com/openai/codex)
## FAQs
No. The hosted DexPaprika MCP server is keyless, so there is no bearer token or OAuth to configure. A free registered key raises the monthly allowance from 50,000 credits to 300,000, and Pro raises it to 5,000,000 at 300 requests a minute; see [rate limits](/knowledge-base/rate-limits).
Streamable HTTP. Point Codex at `https://mcp.dexpaprika.com/streamable-http`. The same server also serves SSE (`/sse`) for clients that prefer it.
Delete the `[mcp_servers.dexpaprika]` block from `~/.codex/config.toml`, or run `codex mcp` to manage configured servers.
# Use DexPaprika with CrewAI
Source: https://docs.dexpaprika.com/ai-integration/crewai
Give a CrewAI agent live DEX market data by connecting the keyless DexPaprika hosted MCP server with MCPServerAdapter. All 17 tools, zero custom code.
CrewAI can use DexPaprika's live DEX data with no custom tool code: its `MCPServerAdapter` connects to our hosted MCP server and hands all 17 tools to your agents. Keyless, no signup.
## Install
```bash theme={null}
pip install 'crewai-tools[mcp]'
```
## Connect the hosted MCP server
`MCPServerAdapter` pulls the tools from `https://mcp.dexpaprika.com/sse` and exposes them as native CrewAI tools:
```python theme={null}
from crewai import Agent, Task, Crew, Process
from crewai_tools import MCPServerAdapter
server = {"url": "https://mcp.dexpaprika.com/sse", "transport": "sse"}
with MCPServerAdapter(server, connect_timeout=60) as tools:
analyst = Agent(
role="DeFi Market Analyst",
goal="Answer on-chain DEX questions with live data",
backstory="Expert in liquidity and trading across 36 chains.",
tools=tools,
)
task = Task(
description="Find the top WETH pools on ethereum and report their 24h volume.",
expected_output="A short list of pools with 24h volume.",
agent=analyst,
)
Crew(agents=[analyst], tasks=[task], process=Process.sequential).kickoff()
```
The `with` block keeps the MCP connection open for the crew's run and closes it afterward. Use the `/sse` transport; it is the tested default.
## Tools and the rationale field
All 17 DexPaprika tools appear as CrewAI tools: networks, pools, tokens, OHLCV, transactions, and cross-chain search. See the [MCP tools reference](/ai-integration/mcp-tools) for the full list.
Every read tool takes a required `rationale` string (1-2 sentences on why you are calling it). It is part of each tool's schema, so a capable model fills it automatically. If a smaller model returns validation errors, prompt it to include a short rationale.
## Resources
* [CrewAI documentation](https://docs.crewai.com)
* [Hosted MCP server](/ai-integration/hosted-mcp-server)
* [MCP tools reference](/ai-integration/mcp-tools)
## FAQs
No. The hosted server is keyless for read access. A free registered key raises the monthly allowance from 50,000 credits to 300,000, and Pro raises it to 5,000,000 at 300 requests a minute; see [rate limits](/knowledge-base/rate-limits).
Use SSE (`https://mcp.dexpaprika.com/sse`) with CrewAI. The server also serves streamable HTTP, but SSE is the tested path through `MCPServerAdapter`.
No. `MCPServerAdapter` discovers the tools from the server, so you get all 17 without defining any yourself.
# Connect DexPaprika MCP to Cursor
Source: https://docs.dexpaprika.com/ai-integration/cursor-ide-integration
Learn how to integrate DexPaprika documentation and MCP server directly into Cursor IDE for enhanced development workflows with real-time crypto data access.
## Integration overview
Connect Cursor IDE to DexPaprika via the hosted MCP server and add docs for rich AI context.
## What is Cursor IDE?
Cursor is a powerful AI-first code editor built on top of VS Code that provides advanced AI assistance for developers. With its built-in Claude integration and MCP (Model Context Protocol) support, Cursor can access external data sources and documentation to provide more intelligent coding assistance.
## Why integrate DexPaprika with Cursor?
Integrating DexPaprika documentation and data with Cursor IDE provides several powerful benefits:
* **Real-time crypto data access** - Get live market data, pool information, and token prices while coding
* **Enhanced AI assistance** - Cursor's AI can reference DexPaprika documentation and data for better code suggestions
* **DeFi development workflow** - Build DeFi applications with direct access to comprehensive blockchain data
* **Documentation context** - AI can reference our API docs, tutorials, and examples while helping you code
***
## Step 1: Install with one click
Opens Cursor and pre-fills the DexPaprika MCP server (`https://mcp.dexpaprika.com/sse`). Click **Install**, then restart Cursor if prompted.
Prefer to configure it manually? Follow the steps below.
1. The button will open Cursor IDE (if not already open)
2. In the MCP server configuration dialog, enter the DexPaprika MCP server URL:
```
https://mcp.dexpaprika.com/sse
```
3. Click "Install" to complete the setup
4. Restart Cursor if prompted to complete the setup
1. Open a new chat with Claude in Cursor (Cmd/Ctrl + L)
2. Ask a question like: "What are the top liquidity pools on Ethereum?"
3. You should see DexPaprika data being retrieved and displayed
4. Try asking about specific networks, tokens, or pools to test the integration
**Can't find the button?** The "Connect to Cursor" button appears on pages where MCP integration is relevant. If you don't see it, you can also manually configure the MCP server using the method below.
## Step 2: Add documentation context
Once you have the MCP server installed, enhance your development experience by adding our documentation to your Cursor workspace for full API reference indexing.
1. In Cursor, go to **Settings** (Cmd/Ctrl + ,)
2. Navigate to **Indexing & Docs** in the left sidebar
3. This section allows you to add custom documentation for AI context
1. In the **Docs** section, click **"+ Add Doc"**
2. Fill out the documentation details:
* **Name**: "DexPaprika API Reference"
* **URL**: [https://docs.dexpaprika.com/api-reference/introduction](https://docs.dexpaprika.com/api-reference/introduction)
3. Click **"Add"** to complete the setup
4. This will index our full API reference for AI context
**Pro tip:** Using the URL [https://docs.dexpaprika.com/api-reference/introduction](https://docs.dexpaprika.com/api-reference/introduction) ensures Cursor indexes our complete API reference, giving you access to all endpoints, parameters, and examples in your AI conversations.
## Manual MCP server configuration
If you can't find the "Connect to Cursor" button or prefer manual setup, you can configure the MCP server manually by following our [hosted MCP server guide](/ai-integration/hosted-mcp-server).
**Need help with manual setup?** Our hosted MCP server guide provides detailed instructions for configuring the DexPaprika MCP server in Cursor, Claude Desktop, and other MCP-compatible tools.
***
## Available features
Once integrated, you can access comprehensive DexPaprika functionality within Cursor:
### Real-time data access
* **Network information** - Get details about supported blockchain networks
* **DEX data** - Access decentralized exchange information and metrics
* **Pool analytics** - Real-time liquidity pool data, volumes, and fees
* **Token information** - Current prices, market data, and token details
* **Search functionality** - Find tokens, pools, and DEXes across networks
### Documentation context
* **API reference** - Complete endpoint documentation and examples
* **SDK guides** - Language-specific integration tutorials
* **Best practices** - Coding patterns and optimization tips
* **Troubleshooting** - Common issues and solutions
***
## Usage examples
### Example 1: Building a DeFi dashboard
Ask Cursor to help you build a DeFi dashboard with real-time data:
```
"Help me create a React component that displays the top 5 liquidity pools
on Ethereum using the DexPaprika API. Include volume, TVL, and price data."
```
Cursor can now:
* Reference our API documentation for correct endpoint usage
* Provide real-time pool data for testing
* Suggest optimal data fetching patterns
* Help with error handling and loading states
### Example 2: Token price monitoring
Create a price monitoring application:
```
"Build a Python script that monitors SOL token prices across different
DEXes and alerts when there are significant price differences."
```
Cursor can:
* Access real-time SOL price data from multiple DEXes
* Reference our historical data tutorials
* Suggest efficient polling strategies
* Help implement price comparison logic
### Example 3: Pool discovery bot
Develop a new pool discovery system:
```
"Create a Node.js application that finds newly created liquidity pools
with high trading volume and sends notifications."
```
Cursor can:
* Use our pool discovery endpoints
* Reference our "Find New Pools" tutorial
* Provide real-time pool data for testing
* Help with notification system implementation
***
***
## Troubleshooting
**Symptoms**: Cursor shows connection errors or timeouts.
**Solutions**:
1. Verify internet connection is stable
2. Check that the MCP server URL is correct: `https://mcp.dexpaprika.com/sse`
3. Restart Cursor completely
4. Try removing and re-adding the MCP server configuration
5. Check Cursor's console for detailed error messages
**Symptoms**: Cursor's AI doesn't reference DexPaprika documentation.
**Solutions**:
1. Ensure documentation files are in your workspace
2. Enable "Use workspace files as context" in AI settings
3. Try asking more specific questions about our API
4. Restart Cursor to refresh context
**Symptoms**: Cursor takes a long time to respond with DexPaprika data.
**Solutions**:
1. Check your internet connection speed
2. Try breaking complex queries into smaller parts
3. Use specific network/token names in your queries
4. Consider using the hosted MCP server for better performance
**Symptoms**: Getting authentication or permission errors.
**Solutions**:
1. DexPaprika API is publicly accessible - no authentication required
2. Check if your network has firewall restrictions
3. Try using the hosted MCP server instead of local installation
4. Contact support if issues persist
***
## Best practices
### For optimal performance
1. **Use specific queries** - Instead of "show me all pools", ask for "top 5 USDC/ETH pools on Ethereum"
2. **Cache frequently used data** - Store common queries locally to reduce API calls
3. **Handle errors gracefully** - Implement proper error handling for network issues
4. **Watch your credit spend** - one request costs one credit against 50,000 a month keyless, and the [DexPaprika console](https://console.dexpaprika.com) shows what you have used this month
### For better AI assistance
1. **Provide context** - Tell Cursor what you're building and your goals
2. **Reference documentation** - Ask Cursor to explain concepts from our docs
3. **Iterate on solutions** - Ask follow-up questions to refine the code
4. **Test with real data** - Use actual DexPaprika data in your development
***
## What's next?
Explore all available endpoints and their capabilities
Learn how to use our official SDKs in your projects
Discover newly created liquidity pools and tokens
Access and analyze historical price and volume data
Zero-setup MCP integration for instant access
Integrate DexPaprika data with ChatGPT
## Need Help?
Connect with our community for real-time support and discussions
Contact our team for technical assistance and custom integrations
**Building something amazing?** Share your Cursor + DexPaprika integrations with our community! We love seeing what developers build with our tools. [Reach out](mailto:support@coinpaprika.com) to showcase your work.
### FAQs
Manually add `https://mcp.dexpaprika.com/sse` via Cursor settings → Tools & Integrations → New MCP server.
Add [https://docs.dexpaprika.com/api-reference/introduction](https://docs.dexpaprika.com/api-reference/introduction) under Indexing & Docs and enable workspace context.
Restart Cursor, re‑add the server, and verify the URL; check the developer console for detailed errors.
# DexPaprika Hosted MCP Server
Source: https://docs.dexpaprika.com/ai-integration/hosted-mcp-server
Access real-time DeFi data in Claude, Cursor, and other MCP-compatible tools without any installation or setup. Our hosted MCP server provides instant access to comprehensive DEX data across 36 blockchains.
## Integration overview
Use the hosted MCP server URL to enable streaming DeFi data in Claude, Cursor, and other MCP clients.
Having trouble connecting? We're here to help - [reach out](mailto:support@coinpaprika.com) and we'll get you up and running.
## Why use our hosted MCP server?
Skip installation entirely. The hosted MCP server is a keyless URL you drop into any MCP client to query live DeFi data across 36 networks.
**What you get instantly:**
* **Zero setup required** - just add the URL to your config
* **Multiple transport options** - SSE, streamable HTTP, and JSON-RPC support
* **Always up-to-date** - we run the server, handle updates, and index new pools
* **Keyless to start** - query without an API key or signup, at 50,000 credits a month per IP
* **Live market data** - current prices, volumes, and pool information
* **36 blockchain networks** - Ethereum, Solana, Base, Arbitrum, and more
Visit [mcp.dexpaprika.com](https://mcp.dexpaprika.com) to explore our hosted MCP server interface and see the available data before integrating.
***
## Quick integration guide
### Claude desktop setup
Add our hosted MCP server to Claude Desktop in just 2 steps:
Find your Claude Desktop configuration file:
* **macOS**: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%/Claude/claude_desktop_config.json`
* **Linux**: `~/.config/Claude/claude_desktop_config.json`
If the file doesn't exist, create it with this content:
```json theme={null}
{
"mcpServers": {
"dexpaprika": {
"url": "https://mcp.dexpaprika.com/sse"
}
}
}
```
If the file already exists, add our server to the existing `mcpServers` object:
```json theme={null}
{
"mcpServers": {
"dexpaprika": {
"url": "https://mcp.dexpaprika.com/sse"
}
}
}
```
Save the file and restart Claude Desktop. You're ready to go!
### Cursor setup
1. Open Cursor IDE
2. Go to **Settings** (Cmd/Ctrl + ,)
3. Navigate to **Tools & Integrations**
4. Click **New MCP server**
This will open the `mcp.json` file. Add the DexPaprika server configuration:
```json theme={null}
{
"mcpServers": {
"dexpaprika": {
"url": "https://mcp.dexpaprika.com/sse"
}
}
}
```
Save the file and restart Cursor if needed.
### ChatGPT integration
Want to use DexPaprika data in ChatGPT? You can integrate our API directly into ChatGPT using Actions:
1. **Create a custom GPT** in ChatGPT (requires Plus or Enterprise subscription)
2. **Add our OpenAPI specification** from [mcp.dexpaprika.com/openapi](https://mcp.dexpaprika.com/openapi)
3. **Start asking crypto questions** directly in your ChatGPT conversations
**Ready to set up ChatGPT?** Follow our complete [ChatGPT Actions integration guide](/ai-integration/chatgpt-actions) for step-by-step instructions.
***
## Connection options
Our hosted MCP server supports multiple transport protocols to ensure compatibility with different clients and use cases:
**Endpoint:** `https://mcp.dexpaprika.com/sse`
**Best for:** Claude Desktop, Cursor, and most MCP clients\
**Benefits:** Real-time streaming updates, excellent browser compatibility, automatic reconnection\
**Use cases:** Live price monitoring, real-time pool updates, continuous market data feeds
This is the recommended option for most users as it provides the smoothest experience with popular AI tools.
**Endpoint:** `https://mcp.dexpaprika.com/streamable-http`
**Best for:** Custom applications, web services, and clients that prefer HTTP streaming\
**Benefits:** Standard HTTP protocol, works well with firewalls, easier debugging\
**Use cases:** Integration with existing web infrastructure, corporate environments with strict network policies
Perfect for developers building custom integrations or working in environments where SSE might be restricted.
**Endpoint:** `https://mcp.dexpaprika.com/json-rpc`
**Best for:** Traditional API integrations, batch processing, simple request-response patterns\
**Benefits:** Familiar REST-like interface, stateless communication, easy to cache\
**Use cases:** Periodic data fetching, batch analysis, integration with existing JSON-RPC systems
Ideal for applications that don't need real-time updates and prefer traditional API communication patterns.
**Getting started?** Use the SSE endpoint (`https://mcp.dexpaprika.com/sse`) for the best experience with Claude Desktop and Cursor. You can always switch protocols later based on your specific needs.
**Need help choosing?** Visit [mcp.dexpaprika.com](https://mcp.dexpaprika.com) to test different connection methods and see which works best for your setup.
***
## Available features
Our hosted MCP server provides comprehensive access to DeFi data:
### Core data access
* **Multi-chain support** - 36 blockchain networks including Ethereum, Solana, Base, Arbitrum, Polygon, and more
* **Real-time prices** - Live token prices and market data
* **Liquidity pools** - Detailed pool information, volumes, and fees
* **DEX analytics** - Trading data across major decentralized exchanges
* **Search functionality** - Find tokens, pools, and DEXes across all networks
New: Batch token pricing via [`GET /networks/{network}/multi/prices`](/api-reference/tokens/get-batched-token-prices-on-a-network) for efficient multi‑token lookups (up to 10 tokens per request).
### Advanced analytics
* **Historical data** - Price history and trading volumes over time
* **Pool monitoring** - Track new pool creation and liquidity changes
* **Cross-chain comparisons** - Compare prices and liquidity across different networks
* **Volume analysis** - Trading volume trends and patterns
***
## Usage examples
Once configured, you can ask Claude or Cursor powerful questions about DeFi data:
### Example queries you can make
| Category | Example Query |
| --------------------------- | ------------------------------------------------------------------------------------------- |
| **Basic market data** | "What are the top 5 liquidity pools by volume today?" |
| **Network information** | "Which blockchain networks does DexPaprika support?" |
| **Token prices** | "What's the current price of SOL across different DEXes?" |
| **Pool analysis** | "Show me the most liquid USDC/ETH pools across all networks and compare their trading fees" |
| **New opportunities** | "Find newly created liquidity pools in the last 24 hours with volume over \$100k" |
| **Cross-chain comparison** | "Compare ETH prices between Ethereum mainnet and Layer 2 solutions like Arbitrum and Base" |
| **DEX performance** | "Which DEX has the highest trading volume on Solana today?" |
| **Token discovery** | "Search for meme tokens with high trading volume in the last hour" |
| **Arbitrage opportunities** | "Find price differences for USDC across different DEXes on Ethereum" |
**Want to explore more?** Visit [mcp.dexpaprika.com](https://mcp.dexpaprika.com) to test the interface, check out our [tutorials](/tutorials/tutorial_intro) for step-by-step guides, or browse the [API documentation](/api-reference/introduction) for technical details.
***
## Benefits over self-hosted solutions
| Feature | Hosted MCP | Self-Hosted MCP |
| ---------------- | --------------------------- | ------------------------------ |
| **Setup time** | \< 2 minutes | 15-30 minutes |
| **Maintenance** | Zero - we handle it | Regular updates required |
| **Reliability** | Managed and monitored by us | Depends on your infrastructure |
| **Edge caching** | Yes | No |
| **Updates** | Automatic | Manual intervention |
***
## Troubleshooting
### Common issues and solutions
**Symptoms**: Claude shows connection errors or timeouts when making requests.
**Solutions**:
1. Check your internet connection
2. Restart Claude Desktop/Cursor
3. Verify the configuration syntax in your config file
4. Try removing and re-adding the server configuration
**Symptoms**: Can't locate the Claude Desktop configuration file.
**Solutions**:
1. Create the directories if they don't exist:
* **macOS**: `mkdir -p ~/Library/Application\ Support/Claude/`
* **Windows**: Create the `Claude` folder in your `%APPDATA%` directory
2. Create the `claude_desktop_config.json` file manually
3. Ensure proper JSON syntax
**Symptoms**: The DexPaprika server doesn't appear in Claude's available tools.
**Solutions**:
1. Verify JSON syntax in your configuration file
2. Restart Claude Desktop completely
3. Check that the server URL is correct: `https://mcp.dexpaprika.com/sse`
4. Try removing and re-adding the server configuration
**Symptoms**: Getting rate limit errors or API failures.
**Solutions**:
1. Our hosted service includes built-in rate limiting protection
2. HTTP 429 means you are going faster than 30 requests a minute, so space the calls out. HTTP 402 means the monthly allowance is spent; [Pro](https://dexpaprika.com/api/pricing) raises it to 5,000,000 credits at 300 requests a minute
3. Try spacing out your requests if making many in quick succession
***
## API coverage
Our hosted MCP server provides access to all DexPaprika API endpoints:
Access all supported blockchain networks and their available decentralized exchanges
Comprehensive pool data including TVL, volume, fees, and token pairs
Real-time token prices, market data, and detailed token information
Price history, volume trends, and historical pool performance
Powerful search across tokens, pools, and DEXes with filtering options
Live data feeds with the latest market information and trading activity
***
## What's next?
Explore all available data endpoints and their capabilities
Learn how to discover newly created liquidity pools and tokens
Want to run your own MCP server? Check out our self-hosted guide
Access and analyze historical price and volume data
## Need help?
Connect with our community and get real-time support from other builders
Reach out directly for technical support or feature requests
**Looking for custom integrations?** Our team can help you integrate DexPaprika data into any application or workflow. [Contact us](mailto:support@coinpaprika.com) to discuss your specific needs.
### FAQs
Not to start. Add the hosted URL to your client config and it calls the DexPaprika API keyless, at 50,000 credits a month per IP. [Registering a free key](https://console.dexpaprika.com) raises that to 300,000.
Start with SSE for streaming and broad compatibility. Use streamable HTTP or JSON‑RPC if your environment restricts SSE.
Confirm the URL, restart the client, and test the endpoint in a browser. If using desktop apps, ensure your config JSON is valid.
Yes, the same limits as any other call: 30 requests a minute against 50,000 credits a month keyless, or 300,000 with a free key. Pro raises that to 300 a minute against 5,000,000 credits. See [rate limits](/knowledge-base/rate-limits).
# DexPaprika for AI agents and AI-assisted development
Source: https://docs.dexpaprika.com/ai-integration/index
Connect AI tools to real-time DEX data across 36 blockchains. Choose from Claude Code plugin, hosted MCP server, IDE integrations, ChatGPT Actions, or direct REST API access. Keyless to start.
## Why DexPaprika + AI?
DexPaprika provides real-time on-chain DEX data covering 36 blockchains, 33M+ tokens, and 36M+ pools, keyless to start. AI agents and AI-assisted IDEs can query token prices, pool data, historical OHLCV, swap transactions, and more, either through MCP (Model Context Protocol) or direct REST API calls.
This page helps you pick the right integration for your setup.
**For AI agents:** fetch [docs.dexpaprika.com/llms.txt](https://docs.dexpaprika.com/llms.txt) for a machine-readable index of this documentation, or [llms-full.txt](https://docs.dexpaprika.com/llms-full.txt) for the full text. To self-onboard against the live server, call the **getCapabilities** MCP tool first: it returns the tool list, network synonyms, and recommended call sequences in one shot.
***
## Fastest path
Add the hosted MCP server to Claude, Cursor, or VS Code. One keyless URL, no install:
```json theme={null}
{
"mcpServers": {
"dexpaprika": {
"url": "https://mcp.dexpaprika.com/sse"
}
}
}
```
Recommended for most setups. Full walkthrough: [hosted MCP server](/ai-integration/hosted-mcp-server).
***
## Choose your integration
Add one keyless URL to Claude Desktop, Cursor, or VS Code. No installation.
Run the MCP server locally via `npx dexpaprika-mcp` for full control.
Two commands, zero config. Built-in MCP access inside Claude Code.
One command. Full API knowledge for Claude Code, Cursor, Cline, and 40+ agents.
`langchain-dexpaprika`: five tools plus a toolkit for Python agents.
One-click install for popular editors, or the config for any MCP client.
One install command. Query and stream data from the terminal.
Call the API directly from any agent framework or custom application.
***
## Compare the options
| | Agent Skills | CLI | Claude Code Plugin | Hosted MCP | Self-hosted MCP | REST API |
| -------------------- | ------------------------------------------- | ------------------------------------- | --------------------------------------------- | ------------------------------- | --------------------------- | ------------------------------------------------ |
| **Setup time** | 10 seconds | 10 seconds | 30 seconds | 1-2 minutes | 10-15 minutes | 0 (just call endpoints) |
| **Install** | `npx skills add` | One curl command | Plugin marketplace | Add URL to config | npm/npx | No install |
| **Works with** | Claude Code, Cursor, Cline, 40+ agents | Any terminal, any agent | Claude Code | Claude Desktop, Cursor, VS Code | Claude Desktop, Cursor | Any HTTP client or agent |
| **How it works** | Agent reads API docs and makes calls itself | Direct CLI commands | MCP tools called by agent | MCP tools called by agent | MCP tools called by agent | Direct HTTP calls |
| **Streaming prices** | Agent can use REST streaming | Built-in `stream` command | No (use REST streaming) | No (use REST streaming) | No (use REST streaming) | Yes via [SSE streaming](/streaming/introduction) |
| **Works offline** | Yes (local files) | Yes (local binary) | No | No | Yes (local server) | No |
| **Best for** | Any agent that can make HTTP/CLI calls | Terminal agents, scripting, pipelines | Claude Code users who want everything bundled | Quick AI IDE setup | Custom/private environments | Agent frameworks, custom apps |
***
## What data can AI agents access?
Every integration exposes the same dataset, delivered as **17 MCP tools** (see the [MCP tools reference](/ai-integration/mcp-tools)) or as REST endpoints:
| Capability | Description |
| ------------------ | ------------------------------------------------------------------------------------- |
| **Token prices** | Real-time USD prices for any token on any supported network |
| **Batch prices** | Up to 10 token prices in a single request |
| **Pool data** | Liquidity, volume, fees, and token pairs for any pool |
| **Pool filtering** | Find pools by volume range, transaction count, or creation date |
| **OHLCV history** | Historical candlestick data, 9 intervals (1m to 24h) |
| **Transactions** | Recent swaps for any pool |
| **DEX listings** | Every DEX on a network with pool counts |
| **Search** | Find tokens, pools, and DEXes by name, symbol, or address across all chains |
| **Network info** | All 36 supported blockchain networks |
| **Live streaming** | Real-time price and pool-reserve updates via [SSE streaming](/streaming/introduction) |
***
## Example prompts
Once connected, try:
* "What's the current price of SOL?"
* "Show me the top 10 pools on Ethereum by volume"
* "Find newly created pools on Base with over \$50k daily volume"
* "Get 30-day OHLCV data for the USDC/ETH pool on Uniswap V3"
* "Compare WETH prices across Ethereum, Arbitrum, and Base"
* "Which DEX has the most pools on Solana?"
***
## Next steps
Every tool, its parameters, pagination, and the getCapabilities onboarding tool
Core concepts: pools, DEXes, OHLCV, and how DexPaprika data works
Complete endpoint documentation with an interactive playground
Real-time price and reserve updates via Server-Sent Events
### FAQs
For terminal work and scripting, start with the CLI. If you use Claude Code, the plugin is the fastest path. For Claude Desktop, Cursor, or VS Code, use the hosted MCP server. For Python agents, use `langchain-dexpaprika`. For custom agents and applications, call the REST API directly.
No. DexPaprika is keyless for public read access: no API key, registration, or authentication needed to start. A free registered key raises the monthly allowance from 50,000 credits to 300,000, and Pro raises it to 5,000,000 at 300 requests a minute; see [rate limits](/knowledge-base/rate-limits).
Yes. For example, use the MCP server in Cursor for interactive development and call the REST API from your application code.
50,000 credits a month without a key, or 300,000 with a free key, at 30 requests a minute. For more, the [Pro API](/api-pro/introduction) is \$99 a month for 5,000,000 credits at 300 requests a minute; [plans and checkout](https://dexpaprika.com/api/pricing) are on the pricing page. One request costs one credit; batch endpoints cost one credit per item.
# DexPaprika tools for LangChain agents
Source: https://docs.dexpaprika.com/ai-integration/langchain
Give a LangChain agent keyless DEX market data with langchain-dexpaprika: five tools plus a toolkit for search, token details, pools, OHLCV, and networks across every supported blockchain. No API key needed to start.
[`langchain-dexpaprika`](https://pypi.org/project/langchain-dexpaprika/) gives a LangChain agent live DEX market data from the [DexPaprika API](https://docs.dexpaprika.com). It is keyless: there is no signup and no environment variable to set, so your agent has DEX data across every supported blockchain (33M+ tokens, 36M+ pools) as soon as you install the package.
The tools are built for LLM consumption: descriptions spell out which parameters exist and where to get their values, error messages quote the API's own allowed-value lists so the model can self-correct, and outputs are compact JSON with long token descriptions trimmed.
## Install
```bash theme={null}
pip install -U langchain-dexpaprika
```
There are no credentials to configure. The DexPaprika API is keyless, so there is no environment variable to set.
## Quickstart
```python theme={null}
from langchain_dexpaprika import DexPaprikaSearch
search = DexPaprikaSearch()
print(search.invoke({"query": "WETH"}))
```
This returns compact JSON with matching tokens (contract address, chain, USD price, liquidity), pools, and DEXes. `search` is the entry point when you only have a ticker: it resolves a symbol into a contract address and network id you can feed into the other tools.
## Tools
| Tool name | Class | What it returns |
| -------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------- |
| `dexpaprika_search` | `DexPaprikaSearch` | Tokens, pools, and DEXes matching a name, symbol, or address. Use it first when you only have a ticker. |
| `dexpaprika_token_details` | `DexPaprikaTokenDetails` | Price, FDV, liquidity, pool count, and 24h/6h/1h volume with buy/sell breakdown for one token on one network. |
| `dexpaprika_token_pools` | `DexPaprikaTokenPools` | Pools where a token trades, sortable by volume, liquidity, transactions, age, price, or 24h price change. |
| `dexpaprika_pool_ohlcv` | `DexPaprikaPoolOHLCV` | Historical OHLCV candles for one pool, intervals from 1m to 24h, up to 366 candles per call. |
| `dexpaprika_networks` | `DexPaprikaNetworks` | Every supported network with its exact id, 24h volume, transactions, and pool counts. |
## Use the toolkit in an agent
`DexPaprikaToolkit` bundles all five tools over one shared HTTP client. This example drives them with an Anthropic model, so install the provider and set its key first (swap in any chat model you prefer):
```bash theme={null}
pip install -U "langchain[anthropic]"
export ANTHROPIC_API_KEY=...
```
```python theme={null}
from langchain.agents import create_agent
from langchain_dexpaprika import DexPaprikaToolkit
toolkit = DexPaprikaToolkit()
agent = create_agent("claude-sonnet-4-5", toolkit.get_tools())
result = agent.invoke(
{"messages": [("user", "Find the most liquid WETH pool on ethereum")]}
)
```
The tool descriptions chain naturally: the agent starts with `dexpaprika_search` to resolve a ticker into a contract address and network id, then feeds those into the other tools.
## Individual tools
Every tool works standalone, sync and async:
```python theme={null}
from langchain_dexpaprika import DexPaprikaPoolOHLCV
ohlcv = DexPaprikaPoolOHLCV()
candles = ohlcv.invoke(
{
"network": "ethereum",
"pool_address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
"start": "2026-07-10",
"interval": "24h",
"limit": 7,
}
)
```
## Prefer MCP or the raw API?
The MCP server exposes the same data as callable tools for Claude, Cursor, and other MCP clients: see the [hosted MCP server](/ai-integration/hosted-mcp-server) and the [MCP tools reference](/ai-integration/mcp-tools). For any framework we do not ship a package for yet, call the [REST API](/api-reference/introduction) directly.
## Resources
* [Package on PyPI](https://pypi.org/project/langchain-dexpaprika/)
* [Source on GitHub](https://github.com/coinpaprika/langchain-dexpaprika) (MIT)
* [DexPaprika API docs](https://docs.dexpaprika.com)
## FAQs
No. The DexPaprika API is keyless, so there is nothing to sign up for and no environment variable to set. A free registered key raises the monthly allowance from 50,000 credits to 300,000, and Pro raises it to 5,000,000 at 300 requests a minute; see [rate limits](/knowledge-base/rate-limits).
Any LangChain chat model. `DexPaprikaToolkit().get_tools()` returns standard LangChain tools, so you can pass them to `create_agent` with an Anthropic, OpenAI, or other provider model.
Same data, different delivery. Use `langchain-dexpaprika` inside a Python LangChain agent; use the [hosted MCP server](/ai-integration/hosted-mcp-server) to expose the tools to Claude, Cursor, or any MCP client without writing code.
# DexPaprika MCP server: DEX data for Claude, Cursor & AI agents
Source: https://docs.dexpaprika.com/ai-integration/mcp
Give AI assistants live DEX data via MCP: pools, tokens and prices on 36 chains. Free to start, no API key. Install locally with npx or use the hosted server.
## Integration overview
Install and configure the DexPaprika MCP server so AI tools can query networks, DEXes, pools, tokens, and search.
## What is MCP?
MCP (Model Context Protocol) is an [open protocol standard created by Anthropic](https://modelcontextprotocol.io/introduction) that gives AI models a unified way to connect to external data sources and tools. The DexPaprika MCP server leverages this protocol to provide AI assistants like Claude with access to real-time crypto and DeFi market data, enabling advanced conversations about blockchain networks, decentralized exchanges (DEXes), liquidity pools, and tokens across the DeFi ecosystem.
**Looking for an easier setup?** If you prefer not to install anything locally, check out our [DexPaprika Hosted MCP Server](/ai-integration/hosted-mcp-server) for instant access with zero installation required.
## Installation Guide
### Prerequisites
Before installing the DexPaprika MCP server, ensure you have:
* [Node.js](https://nodejs.org/) (v16 or higher) installed on your system
* [npm](https://www.npmjs.com/) (comes with Node.js) or [yarn](https://yarnpkg.com/) package manager
* [Claude Desktop](https://claude.ai/desktop) or [Cursor](https://cursor.sh/) installed if you want to use the MCP server with these applications
### Installation Options
#### Option 1: Global Installation (Recommended)
Installing the DexPaprika MCP server globally makes it available throughout your system:
```bash theme={null}
npm install -g dexpaprika-mcp
```
After installation, you can start the server by running:
```bash theme={null}
dexpaprika-mcp
```
#### Option 2: Use with npx (No Installation)
Alternatively, you can run the server directly without installation using npx:
```bash theme={null}
npx dexpaprika-mcp@latest
```
This is useful for trying out the server without permanently installing it.
### Verification
To verify that your installation was successful, run:
```bash theme={null}
dexpaprika-mcp --version
```
You should see the current version number of the DexPaprika MCP server.
## Configuration
### Claude Desktop Configuration
To use the DexPaprika MCP server with Claude Desktop:
1. Download and install [Claude Desktop](https://claude.ai/desktop) if you haven't already
2. Locate your Claude Desktop configuration file:
* **macOS**: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%/Claude/claude_desktop_config.json`
3. If the file doesn't exist, create it with the following content:
```json theme={null}
{
"mcpServers": {
"dexpaprika": {
"command": "dexpaprika-mcp"
}
}
}
```
4. If the file already exists, add the DexPaprika configuration to the existing `mcpServers` object:
```json theme={null}
"dexpaprika": {
"command": "dexpaprika-mcp"
}
```
5. Save the file and restart Claude Desktop.
6. To verify the configuration, open Claude Desktop and try asking a question about cryptocurrency data, such as "What are the top liquidity pools on Ethereum?"
Make sure you have the latest version of our package by running `npm update -g dexpaprika-mcp`
### Cursor Configuration
If you're using Cursor IDE with Claude:
1. Download and install [Cursor](https://cursor.sh/) if you haven't already
2. Open Cursor and click on the Claude button in the sidebar
3. Click the settings (gear) icon and select "Add MCP Server"
4. Fill in the following information:
* **Server name**: `dexpaprika`
* **Type**: `command` (select from dropdown)
* **Command to run**: `npx dexpaprika-mcp@latest`
5. Click "Add" to save the configuration
6. Alternatively, Cursor will automatically use any MCP servers configured in Claude Desktop
## Troubleshooting
If you encounter issues with the DexPaprika MCP server:
1. **Server not found errors**:
* Ensure the server is installed correctly using `npm list -g dexpaprika-mcp`
* Try reinstalling with `npm install -g dexpaprika-mcp`
2. **Connection errors**:
* Check that your internet connection is active
* Verify that no firewall is blocking the connection
3. **Configuration errors**:
* Double-check your configuration file syntax
* Ensure the path to the configuration file is correct for your OS
4. **Command not found errors**:
* Ensure Node.js is installed and in your PATH
* Try using the full path to the npm or npx executables
## Features
The DexPaprika MCP server provides access to:
* Blockchain network information across multiple chains
* Decentralized exchange (DEX) data
* Liquidity pool details and metrics
* Token information and market data
* Price and volume analytics for tokens and pools
* Comprehensive search capabilities across DeFi entities
## Available tools
New to the server? Call **getCapabilities** first. In one call it returns the full tool list, network name synonyms, recommended call sequences, and common pitfalls, so an agent can self-onboard before making any data calls.
The DexPaprika MCP server exposes 17 tools.
**Discovery and onboarding**
* **getCapabilities** - Agent onboarding: supported workflows, network synonyms, pitfalls, and recommended call sequences. Call this first.
* **getNetworks** - List every supported blockchain network with 24h volume and indexing stats
* **getStats** - Platform-wide totals: networks, DEXes, pools, and tokens indexed
* **search** - Search across all networks for tokens, pools, and DEXes by name, symbol, or address
**Pools**
* **getNetworkPools** - Top liquidity pools on a network, ranked by 24h volume by default
* **getNetworkPoolsFilter** - Filter pools on a network by volume, liquidity, transactions, or creation time
* **getDexPools** - Pools on a specific DEX within a network (backed by pool search with a `dex_name` filter, since the old DEX pools endpoint returns `410 Gone`)
* **getPoolDetails** - Full details for one pool: tokens, price, liquidity, and 24h volume
* **getPoolOHLCV** - Historical OHLCV (candlestick) data for a pool (1m to 24h intervals)
* **getPoolTransactions** - Recent swaps for a pool (`from`/`to` are UNIX timestamps, capped to the last 7 days)
**Tokens**
* **getTokenDetails** - Metadata and multi-timeframe price and volume metrics for one token
* **getTokenPools** - Liquidity pools containing a specific token on a network
* **getTokenMultiPrices** - USD prices for up to 10 tokens in one batched call
* **filterNetworkTokens** - Filter tokens on a network by volume, liquidity, FDV, transactions, or creation time
* **getTopTokens** - Top tokens on a network ranked by volume, liquidity, transactions, FDV, or 24h change
**DEXes**
* **getNetworkDexes** - DEXes operating on a specific network
**Feedback** (hosted server only)
* **submitFeedback** - Report a problem, unexpected response, or missing data back to the DexPaprika team
**Agent notes:** every read tool takes a required `rationale` string (1-2 sentences on what triggered the call). The pool and token list tools (`getNetworkPools`, `getNetworkPoolsFilter`, `getDexPools`, `getTokenPools`, `getTopTokens`, `filterNetworkTokens`) return rows under `results` with cursor pagination (`has_next_page` + `next_cursor`), and sort with canonical `sort_by` / `sort_dir`.
For full parameters, defaults, and return shapes for every tool, see the [MCP tools reference](/ai-integration/mcp-tools).
## Usage Examples
Once configured, you can ask Claude questions about DeFi data. Here are some example prompts:
### General Market Data
* "What are the top 5 liquidity pools across all networks by volume?"
### Network-Specific Queries
* "Which blockchain networks are supported by DexPaprika?"
* "What are the top DEXes on the Solana network?"
* "Show me the top 10 liquidity pools on Ethereum, ordered by volume."
### DEX and Pool Analysis
* "What are the most active pools on Uniswap V3?"
* "Show me details about the USDC/ETH pool on Uniswap V3."
* "Compare the trading volume between PancakeSwap and Uniswap."
### Token Information
* "What's the current price of SOL in the Raydium pool on Solana?"
* "Find all pools that include the SHIB token."
### Search Functionality
* "Search for pools related to 'Bitcoin'"
* "Find tokens with 'Pepe' in their name"
## Advanced Queries
You can also ask Claude to perform more complex analysis:
* "Compare the liquidity and volume of the top 3 DEXes on Ethereum"
* "What's the price difference of ETH between Uniswap and SushiSwap?"
* "Show me the tokens with the highest price volatility in the last 24 hours"
* "Analyze the trading volume trends for BNB on PancakeSwap"
## Support
If you need further assistance, you can:
* Contact DexPaprika support by [email](mailto:support@coinpaprika.com)
* Join the [Discord community](https://discord.gg/DhJge5TUGM)
### FAQs
No. The underlying DexPaprika API is public; the MCP server proxies public endpoints.
Claude Desktop reads `claude_desktop_config.json`; Cursor can add the same server via “Add MCP Server” or inherit Claude’s config.
Use absolute paths to `uvx`/`npx` in the config; many desktop apps don’t inherit shell `PATH`.
Yes. Every tool is a read-only data query; the server cannot modify on-chain state or your data, so no flag is needed.
# Connect DexPaprika MCP to any client
Source: https://docs.dexpaprika.com/ai-integration/mcp-clients
Connect the keyless DexPaprika MCP server to any MCP-compatible client (Windsurf, Cline, Zed, and more). One canonical config: a remote SSE/streamable-HTTP URL, or a local stdio command.
DexPaprika ships a standard MCP server, so it works with any MCP-compatible client, not just the ones with a dedicated guide. There are two ways to connect, and most clients accept the first.
## Option 1: Remote server (recommended)
Point your client at the hosted server. No install, keyless.
```json theme={null}
{
"mcpServers": {
"dexpaprika": {
"url": "https://mcp.dexpaprika.com/sse"
}
}
}
```
`/sse` is the recommended transport. The same server also serves streamable HTTP at `https://mcp.dexpaprika.com/streamable-http` for clients that prefer it.
## Option 2: Local server (stdio)
Run the server on your machine and have the client launch it:
```json theme={null}
{
"mcpServers": {
"dexpaprika": {
"command": "npx",
"args": ["-y", "dexpaprika-mcp@latest"]
}
}
}
```
## Where to put the config
The `mcpServers` shape above is what **Claude Desktop, Cursor, VS Code, Cline, and Windsurf** use. The file location differs per client (for example Windsurf keeps it in `~/.codeium/windsurf/mcp_config.json`, Cline in its MCP settings pane), so check your client's MCP docs for the exact path, then paste one of the blocks above.
A few clients use a different shape:
| Client | Config format |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| Claude Desktop, Cursor, VS Code, Cline, Windsurf | `mcpServers` JSON (above) |
| Codex CLI | TOML in `~/.codex/config.toml` (see the [Codex guide](/ai-integration/codex)) |
| Zed | `context_servers` in `settings.json` (Zed's own key), pointing at the same URL or command |
We have step-by-step guides for [Cursor](/ai-integration/cursor-ide-integration), [VS Code](/ai-integration/vscode-ide-integration), [Claude Code](/ai-integration/claude-code-plugin-guide), and [Codex](/ai-integration/codex). For anything else, the config above is all you need.
## What you get
17 tools across networks, pools, tokens, OHLCV, transactions, and cross-chain search. Start with `getCapabilities` for the runtime manifest, or read the [MCP tools reference](/ai-integration/mcp-tools).
## FAQs
If it supports MCP, yes. DexPaprika is a standard MCP server. Use the remote `url` config for a hosted connection, or the stdio `command` config to run it locally, in whatever place your client keeps MCP servers.
Remote (`url`) is simplest: no install, always up to date, keyless. Choose local (stdio) if your client does not support remote MCP servers, or you want to run the server in your own environment.
No. The server is keyless for read access. A free registered key raises the monthly allowance from 50,000 credits to 300,000, and Pro raises it to 5,000,000 at 300 requests a minute; see [rate limits](/knowledge-base/rate-limits).
# DexPaprika MCP tools reference
Source: https://docs.dexpaprika.com/ai-integration/mcp-tools
Complete reference for all 17 DexPaprika MCP tools: parameters, defaults, pagination, sorting, and return shapes. The machine-readable manifest an AI agent needs to plan calls before connecting.
This page lists every tool the DexPaprika MCP server exposes, with parameters, defaults, and return shapes, so an agent (or a developer) can plan calls before connecting.
**Agents: call `getCapabilities` first.** In one call it returns the workflow patterns, network name synonyms, and common pitfalls below as structured JSON, so the server describes itself at runtime. This page is the human-readable mirror of that manifest.
The server exposes **17 tools** across five groups. All data is read-only; `submitFeedback` is the only write tool and is available on the hosted server only.
## Conventions that apply to every tool
These rules hold across the whole surface. Read them once and the individual tools get much shorter.
| Convention | What to know |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`rationale` (required)** | Every read tool takes a `rationale` string (20-500 chars): 1-2 sentences on what triggered the call and how you will use the result. It is logged to improve the server and never shown to end users. |
| **Network ids** | Tools that take a `network` want the canonical slug (`ethereum`, `solana`, `base`). Call `getNetworks` for the list, or `getCapabilities` for the synonym map that resolves `eth`, `matic`, `bnb`, and similar to the right slug. |
| **Pagination** | List tools return rows under `results` with **cursor pagination**: read `has_next_page` and pass `next_cursor` back as `cursor` for the next page. Two older tools (`getNetworkDexes` and `getPoolTransactions`) use 1-indexed `page` numbers instead. |
| **Sorting** | Over MCP use `sort_by` (a field name) and `sort_dir` (`asc`/`desc`, default `desc`); `order_by`/`sort` are also accepted and mapped. Default sort is `volume_usd_24h`. **Calling the REST API directly? Use `order_by`/`sort`** ([REST reference](/api-reference/introduction)): those are the names it requires, and an unrecognized parameter name is silently ignored, so a wrong name looks like a working sort while you actually get the default order. |
| **Rate limits** | 30 requests/min on the free tiers, 300/min on Pro. Over the limit returns HTTP 429; back off and retry after a short pause. See [rate limits](/knowledge-base/rate-limits). |
| **Output** | Every tool returns both `content[0].text` (a JSON string) and `structuredContent` (validated against the tool's output schema). Prefer `structuredContent`. |
***
## Discovery and onboarding
### getCapabilities
Agent onboarding in one call: supported workflows, network name synonyms, common pitfalls, and recommended call sequences. Read it once at the start of a session. Returns static onboarding docs, not live market data.
No parameters beyond `rationale`.
### getNetworks
The full list of blockchain networks DexPaprika indexes, each with 24h volume, transaction counts, and pool counts. Start here (or `getCapabilities`) because every other DEX query needs a valid network slug.
No parameters beyond `rationale`.
### getStats
Platform-wide totals: number of networks, DEXes, pools, and tokens indexed. Ecosystem-wide counts, not per-network figures (use `getNetworks` for the per-chain breakdown).
No parameters beyond `rationale`.
### search
Search across **all** networks at once for tokens, pools, and DEXes by name, symbol, or address. Returns three arrays: `tokens`, `pools`, `dexes`. This is the cross-chain entry point; once you have a network slug, switch to the network-scoped tools.
| Param | Required | Type | Default | Notes |
| ------- | -------- | ------ | ------- | ----------------------------------------------------------------- |
| `query` | yes | string | | Name, symbol, or address (for example `uniswap`, `PEPE`, `0x...`) |
| `limit` | no | number | | Max results per category, applied client-side |
***
## Pools
### getNetworkPools
The top liquidity pools on one network, ranked by 24h volume by default. The primary pool-discovery tool for a whole chain.
| Param | Required | Type | Default | Notes |
| ---------- | -------- | ------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `network` | yes | string | | Network slug from `getNetworks` |
| `limit` | no | number | 10 | Max 100 |
| `cursor` | no | string | | `next_cursor` from a previous response |
| `sort_by` | no | enum | `volume_usd_24h` | `volume_usd_24h`, `volume_usd_7d`, `volume_usd_30d`, `liquidity_usd`, `txns_24h`, `price_usd`, `price_change_percentage_24h`, `price_change_percentage_6h`, `price_change_percentage_1h`, `price_change_percentage_5m`, `created_at` |
| `sort_dir` | no | `asc`/`desc` | `desc` | |
Rows return under `results` with cursor pagination.
### getNetworkPoolsFilter
Pools on one network filtered by exact thresholds for volume, liquidity, transaction count, price change, or creation time. Choose this over `getNetworkPools` when the user gives numeric constraints or a time window.
| Param | Required | Type | Default | Notes |
| ------------------------------------------ | -------- | ------------ | ---------------- | -------------------------------- |
| `network` | yes | string | | Network slug |
| `limit` | no | number | 50 | Max 100 |
| `cursor` | no | string | | Cursor pagination |
| `volume_24h_min` / `volume_24h_max` | no | number | | 24h volume in USD |
| `volume_7d_min` / `volume_7d_max` | no | number | | 7d volume in USD |
| `liquidity_usd_min` / `liquidity_usd_max` | no | number | | Pool liquidity in USD |
| `txns_24h_min` | no | number | | Minimum 24h transactions |
| `price_change_percentage_24h_min` / `_max` | no | number | | 24h price change, percent |
| `price_change_percentage_6h_min` / `_max` | no | number | | 6h price change, percent |
| `price_change_percentage_1h_min` / `_max` | no | number | | 1h price change, percent |
| `price_change_percentage_5m_min` / `_max` | no | number | | 5m price change, percent |
| `created_after` / `created_before` | no | number | | UNIX timestamp |
| `sort_by` | no | enum | `volume_usd_24h` | Same fields as `getNetworkPools` |
| `sort_dir` | no | `asc`/`desc` | `desc` | |
To find new pools: set `created_after` and sort by `created_at` descending. To find movers, pass a signed percentage bound: `price_change_percentage_1h_max: -20` returns pools down 20 percent or more over the last hour.
### getDexPools
The pools that belong to one specific DEX on one network (for example all Uniswap v3 pools on ethereum). Narrower than `getNetworkPools`.
| Param | Required | Type | Default | Notes |
| ---------- | -------- | ------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `network` | yes | string | | Network slug |
| `dex` | yes | string | | DEX id from `getNetworkDexes`, its `dex_id` field (for example `uniswap_v3`), case-insensitive. A display name (`Uniswap V3`) returns no rows instead of an error |
| `limit` | no | number | 10 | Max 100 |
| `cursor` | no | string | | Cursor pagination |
| `sort_by` | no | enum | `volume_usd_24h` | Same pool sort fields as `getNetworkPools` |
| `sort_dir` | no | `asc`/`desc` | `desc` | |
The REST endpoint behind this tool changed. `GET /networks/{network}/dexes/{dex}/pools` was removed and returns `410 Gone`, so the tool now calls `GET /networks/{network}/pools/search` with the `dex_name` filter. Two consequences: there is no `page` parameter any more, and the legacy sort values (`volume_usd`, `transactions`, `last_price_change_usd_24h`) are mapped to their canonical names before the request goes out. Rows arrive under `results` with `has_next_page` and `next_cursor`, and the 24h volume field on each row is `volume_usd_24h`.
### getPoolDetails
Full details for one pool by address: the two tokens, current price, liquidity, 24h volume, and transaction counts. The live snapshot only, use `getPoolOHLCV` for history.
| Param | Required | Type | Default | Notes |
| -------------- | -------- | ------- | ------- | ----------------------------------------------------- |
| `network` | yes | string | | Network slug |
| `pool_address` | yes | string | | Pool address |
| `inversed` | no | boolean | false | Invert the price ratio for the other pair perspective |
### getPoolOHLCV
Historical open/high/low/close/volume candles for one pool across a date range and interval. Historical time series, use `getPoolDetails` for the single current price.
| Param | Required | Type | Default | Notes |
| -------------- | -------- | ------- | ------- | --------------------------------------------------------- |
| `network` | yes | string | | Network slug |
| `pool_address` | yes | string | | Pool address |
| `start` | yes | string | | UNIX timestamp, RFC3339, or `yyyy-mm-dd` |
| `end` | no | string | | Max 1 year from `start` |
| `limit` | no | number | 100 | Max 366 |
| `interval` | no | enum | `24h` | `1m`, `5m`, `10m`, `15m`, `30m`, `1h`, `6h`, `12h`, `24h` |
| `inversed` | no | boolean | false | Invert the price ratio |
### getPoolTransactions
The recent individual swap transactions for one pool, newest first. Per-trade records, not aggregated candles.
| Param | Required | Type | Default | Notes |
| -------------- | -------- | ------ | ------- | ---------------------------------------------------------------- |
| `network` | yes | string | | Network slug |
| `pool_address` | yes | string | | Pool address |
| `page` | no | number | 1 | 1-indexed, up to 100 pages |
| `limit` | no | number | 10 | Max 100 |
| `cursor` | no | string | | Transaction id for cursor pagination |
| `from` / `to` | no | number | | UNIX epoch seconds. Results are always capped to the last 7 days |
***
## Tokens
### getTokenDetails
Metadata and multi-timeframe price and volume metrics for one token by contract address, including website, Twitter, and Telegram links. If you only have a symbol, call `search` first to resolve the address.
| Param | Required | Type | Default | Notes |
| --------------- | -------- | ------ | ------- | ---------------------- |
| `network` | yes | string | | Network slug |
| `token_address` | yes | string | | Token contract address |
### getTokenPools
The liquidity pools that contain a specific token on one network. The token filter is network-scoped, so run `search` first if you do not know the network.
| Param | Required | Type | Default | Notes |
| --------------- | -------- | ------------ | ---------------- | ----------------------------------------------------------------- |
| `network` | yes | string | | Network slug |
| `token_address` | yes | string | | An unknown address returns an empty `results` array, not an error |
| `limit` | no | number | 10 | Max 100 |
| `cursor` | no | string | | Cursor pagination |
| `sort_by` | no | enum | `volume_usd_24h` | Same pool sort fields as `getNetworkPools` |
| `sort_dir` | no | `asc`/`desc` | `desc` | |
`inversed`/`reorder` and `paired_token_address`/`address` are no longer supported (the underlying endpoint changed). Passing them returns a structured error. To flip a price, compute `1/price` client-side; for pair queries, filter `results[].tokens` client-side.
### getTokenMultiPrices
Current USD prices for up to 10 tokens on the same network in one batched call. Tokens that cannot be priced come back under `missing_tokens` rather than being dropped, so check that list for partial failures.
| Param | Required | Type | Default | Notes |
| --------- | -------- | --------- | ------- | ------------------------------------------- |
| `network` | yes | string | | Network slug |
| `tokens` | yes | string\[] | | 1 to 10 token addresses on the same network |
### filterNetworkTokens
Tokens on one network filtered by exact thresholds for volume, liquidity, FDV, transactions, or creation time. Choose this over `getTopTokens` when the user gives numeric constraints.
| Param | Required | Type | Default | Notes |
| ----------------------------------------- | -------- | ------------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `network` | yes | string | | Network slug |
| `limit` | no | number | 50 | Max 100 |
| `cursor` | no | string | | Cursor pagination |
| `volume_24h_min` / `volume_24h_max` | no | number | | 24h volume in USD |
| `liquidity_usd_min` / `liquidity_usd_max` | no | number | | Token liquidity in USD |
| `fdv_min` / `fdv_max` | no | number | | Fully diluted valuation in USD |
| `txns_24h_min` | no | number | | Minimum 24h transactions |
| `created_after` / `created_before` | no | number | | UNIX timestamp |
| `sort_by` | no | enum | `volume_usd_24h` | `volume_usd_24h`, `volume_usd_7d`, `volume_usd_30d`, `liquidity_usd`, `txns_24h`, `fdv_usd`, `price_change_percentage_24h`, `created_at` |
| `sort_dir` | no | `asc`/`desc` | `desc` | |
### getTopTokens
The top tokens on one network ranked by volume, liquidity, transactions, FDV, or 24h price change.
| Param | Required | Type | Default | Notes |
| ---------- | -------- | ------------ | ---------------- | ----------------------------------------------- |
| `network` | yes | string | | Network slug |
| `limit` | no | number | 50 | Max 100 |
| `cursor` | no | string | | Cursor pagination |
| `sort_by` | no | enum | `volume_usd_24h` | Same token sort fields as `filterNetworkTokens` |
| `sort_dir` | no | `asc`/`desc` | `desc` | |
Ranking by raw price is not supported and falls back to volume.
***
## DEXes
### getNetworkDexes
The DEXes (exchanges) operating on one specific network, for example Uniswap on ethereum or Raydium on solana. Use the returned `dex_id` with `getDexPools`. Each row carries both `dex_id` and `dex_name`.
| Param | Required | Type | Default | Notes |
| ---------- | -------- | ------------ | ------- | ------------ |
| `network` | yes | string | | Network slug |
| `page` | no | number | 1 | 1-indexed |
| `limit` | no | number | 10 | Max 100 |
| `sort_by` | no | enum | | Only `pool` |
| `sort_dir` | no | `asc`/`desc` | `desc` | |
***
## Feedback (hosted server only)
### submitFeedback
Report a problem, unexpected response, or missing data back to the DexPaprika team. Available on the hosted server (`mcp.dexpaprika.com`) only; the self-host build does not include it. Unlike the read tools, it takes no `rationale`, the fields below are the context.
| Param | Required | Type | Default | Notes |
| ----------------- | -------- | --------- | ------- | ------------------------------------------------- |
| `goal` | yes | string | | What you were trying to accomplish (10-500 chars) |
| `attempted_tools` | no | string\[] | | Tools you tried before getting stuck |
| `blocked_at` | no | string | | Where exactly you got blocked |
| `expected` | no | string | | What you expected to happen |
| `observed` | no | string | | What actually happened |
| `severity` | no | enum | `minor` | `blocker`, `major`, `minor`, `nit` |
***
## Recommended call sequences
Common tasks map to short tool chains. `getCapabilities` returns this list at runtime.
| Task | Sequence |
| ---------------------------------------- | -------------------------------------------------------------------- |
| Discover supported chains | `getNetworks` |
| Top pools on a chain | `getNetworks` then `getNetworkPools` |
| Pools above a volume/liquidity threshold | `getNetworks` then `getNetworkPoolsFilter` |
| Newly created pools | `getNetworkPoolsFilter` with `created_after`, sort `created_at` desc |
| Token profile and its venues | `getTokenDetails` then `getTokenPools` |
| Pools on one DEX | `getNetworkDexes` then `getDexPools` with the `dex_id` |
| Batch price snapshot | `getTokenMultiPrices` (max 10 tokens) |
| Top or filtered tokens | `getTopTokens` or `filterNetworkTokens` |
| Historical price chart | `getPoolOHLCV` with `start` and `interval` |
| Raw swap feed | `getPoolTransactions` with `from`/`to` |
| Find something across all chains | `search` |
## Common pitfalls
* **List tools return rows under `results`** (not `pools`/`tokens`/`data`) with cursor pagination (`has_next_page` + `next_cursor`), not page numbers. This applies to `getNetworkPools`, `getNetworkPoolsFilter`, `getDexPools`, `getTokenPools`, `getTopTokens`, and `filterNetworkTokens`.
* **`getDexPools` reads a filter, not a path.** The REST endpoint it used to wrap was removed and returns `410 Gone`; the tool now calls pool search with `dex_name`. If you were building the old URL by hand, stop: there is no `page`, and the 24h volume field is `volume_usd_24h`.
* **`getTokenPools` is network-scoped.** The cross-network `search` cannot filter by token. An unknown `token_address` returns empty `results`, not an error. The old pair-flip and second-token parameters were removed and now return a structured error.
* **Sort field names are canonical.** Prefer `volume_usd_24h`, `txns_24h`, `price_change_percentage_24h`, `fdv_usd`. Short legacy names are auto-mapped. Tokens cannot be ordered by raw price.
* **Only the short price change windows are pool-only.** `price_change_percentage_6h`, `_1h` and `_5m` sort the four tools that return pool rows: `getNetworkPools`, `getNetworkPoolsFilter`, `getDexPools` and `getTokenPools`. Only `getNetworkPoolsFilter` takes them as `_min` / `_max` bounds. The token tools (`getTopTokens`, `filterNetworkTokens`) sort by `price_change_percentage_24h` and nothing shorter, and token rows never include the three short fields.
* **`filterNetworkTokens` has no price-change bound.** The REST endpoint behind it, `GET /networks/{network}/tokens/search`, does accept `price_change_percentage_24h_min` / `_max`, and the tool does not surface them. Call the endpoint directly when you need to screen tokens by 24h move.
* **`getTokenMultiPrices` is capped at 10 tokens** per request.
* **`getPoolTransactions` `from`/`to` are UNIX seconds** and results are always capped to the last 7 days.
* **Token addresses must match the network.** Do not send a Solana address to an ethereum query.
## FAQs
17: sixteen read-only data tools plus `submitFeedback`, which is available on the hosted server only. Call `getCapabilities` for the live list.
No. The hosted server is keyless for read access. A free registered key raises the monthly allowance from 50,000 credits to 300,000, and Pro raises it to 5,000,000 at 300 requests a minute; see [rate limits](/knowledge-base/rate-limits).
Every read tool requires a short `rationale` string describing why you are calling it. It is logged to improve the server and is never shown to end users. Include no secrets or personal data.
Most list tools use cursor pagination: read `has_next_page` and pass `next_cursor` back as `cursor`. `getNetworkDexes` and `getPoolTransactions` are the two that still use 1-indexed `page` numbers.
# Use DexPaprika in n8n
Source: https://docs.dexpaprika.com/ai-integration/n8n
Two ways to use live DEX market data in n8n: the dedicated n8n-nodes-dexpaprika community node with typed operations, or the built-in MCP Client Tool pointed at the keyless hosted MCP server. No API key needed to start.
There are two ways to pull live DexPaprika data into n8n, and both are keyless:
* **The dedicated `n8n-nodes-dexpaprika` community node** gives you typed operations (token prices, pools, OHLCV, search) you can drop straight into a workflow or hand to an AI Agent as a tool.
* **The built-in MCP Client Tool node** connects an AI Agent to all 17 DexPaprika tools over the hosted MCP server with nothing to install.
Use the community node when you want explicit, deterministic steps in a workflow. Use the MCP Client Tool when you want a model to pick from the full tool set on its own.
## Option 1: the DexPaprika community node
`n8n-nodes-dexpaprika` is published on npm and carries the `n8n-community-node-package` tag, so it shows up in n8n's community node search.
### Install
On a self-hosted n8n instance:
1. Go to **Settings > Community Nodes**.
2. Click **Install**.
3. Enter `n8n-nodes-dexpaprika` and confirm.
The **DexPaprika** node then appears in the node panel.
Community nodes install on self-hosted n8n. We are submitting the node to the n8n Creator Portal for the verified badge, which also makes verified nodes available on n8n Cloud.
### Operations
The node covers three resources:
* **Token**: search, get details, get USD prices for up to 10 tokens at once, get top tokens on a network.
* **Pool**: get top pools on a network, get pool details, get historical OHLCV candles.
* **Network**: list networks, list DEXes on a network, get platform stats.
It is also marked `usableAsTool`, so you can wire it into an **AI Agent** node's **Tool** input and let the agent call these operations directly.
## Option 2: the built-in MCP Client Tool node
If you would rather not install anything, n8n's **MCP Client Tool** node connects an AI Agent to the hosted server and exposes all 17 tools.
1. In your workflow, add an **AI Agent** node (Tools Agent).
2. Add an **MCP Client Tool** node and connect its output into the AI Agent node's **Tool** input.
3. On the MCP Client Tool node, create an MCP credential:
* **Transport:** SSE
* **SSE Endpoint:** `https://mcp.dexpaprika.com/sse`
* **Authentication:** None
4. Set **Tools to Include** to **All**.
The agent can now call any of the 17 DexPaprika tools when it needs live data.
Use the **SSE** transport. n8n also has an HTTP Streamable option, but an open issue (n8n-io/n8n#24967) reports the transport selector being ignored on some builds, so prefer SSE until it is confirmed fixed on your version.
## Tools and the rationale field
The hosted server exposes all 17 DexPaprika tools: networks, pools, tokens, OHLCV, transactions, and cross-chain search. See the [MCP tools reference](/ai-integration/mcp-tools) for the full list.
Every read tool takes a required `rationale` string. It is part of each tool's schema, so the agent model fills it automatically. If you drive the agent with a smaller model and hit validation errors, add an instruction telling it to include a short rationale.
## Example
Wire either node into a Tools Agent and prompt it with, for example, "What are the top 5 pools on Base by 24h volume?" The agent calls the top-pools operation and returns live results, which you can pass to any downstream n8n node (a message, a spreadsheet, a database).
## Resources
* [n8n-nodes-dexpaprika on npm](https://www.npmjs.com/package/n8n-nodes-dexpaprika)
* [n8n community nodes docs](https://docs.n8n.io/integrations/community-nodes/installation/)
* [n8n MCP Client Tool node docs](https://docs.n8n.io)
* [Hosted MCP server](/ai-integration/hosted-mcp-server)
* [MCP tools reference](/ai-integration/mcp-tools)
## FAQs
No. The hosted server and the community node both read keyless. A free registered key raises the monthly allowance from 50,000 credits to 300,000, and Pro raises it to 5,000,000 at 300 requests a minute; see [rate limits](/knowledge-base/rate-limits).
The community node gives typed operations you can place directly in a workflow or attach to an AI Agent. The built-in MCP Client Tool needs no install and exposes all 17 tools to an agent. Pick the node for deterministic steps, the MCP Client Tool for open-ended agent use.
On self-hosted n8n, go to Settings > Community Nodes, click Install, and enter `n8n-nodes-dexpaprika`. Verified nodes are also available on n8n Cloud; our Creator Portal submission is in progress.
Use SSE (`https://mcp.dexpaprika.com/sse`) for now. HTTP Streamable exists but has a known transport-selection bug on some n8n builds.
# Install DexPaprika agent skills
Source: https://docs.dexpaprika.com/ai-integration/skills
Add the DexPaprika skill to Claude Code, Cursor, or any compatible AI agent with one command. Gives your agent full knowledge of the DexPaprika REST API, CLI, and streaming service.
## What are agent skills?
Agent skills are reusable instruction files that teach AI agents how to use a specific tool or API. When you install the DexPaprika skill, your AI agent gets a complete reference of every endpoint, CLI command, streaming parameter, and common workflow, so it can make correct API calls without guessing.
Skills work with Claude Code, Cursor, Cline, OpenCode, and [many other AI agents](https://github.com/vercel-labs/skills). One install command, and no API key needed to start.
***
## What's included
The DexPaprika skill installs the following files into your project:
| File | What it provides |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SKILL.md` | Full API reference: all REST endpoints, CLI commands, MCP setup, streaming API, SDKs, common workflows, token addresses, pagination, error handling |
| `references/openapi.yml` | Complete OpenAPI 3.1 specification with all schemas, parameters, and response types |
| `references/cli-reference.md` | Every `dexpaprika-cli` command with flags, examples, and output formats |
| `references/streaming-api.md` | SSE streaming documentation: GET/POST endpoints, response fields, connection handling |
Your AI agent reads `SKILL.md` automatically. The reference files are loaded on demand when the agent needs deeper detail (like exact response schemas or CLI flag options).
***
## Install
### One command
```bash theme={null}
npx skills add github.com/coinpaprika/skills/ --skill dexpaprika-api
```
This copies the skill files into `.claude/skills/dexpaprika-api/` in your current project.
To install both CoinPaprika and DexPaprika skills at once, drop the `--skill` flag:
```bash theme={null}
npx skills add github.com/coinpaprika/skills/
```
### What happens
1. The `skills` CLI (by [Vercel Labs](https://github.com/vercel-labs/skills)) fetches the skill from GitHub
2. Files are copied to `.claude/skills/dexpaprika-api/`
3. On next startup, your AI agent auto-discovers and loads the skill
Your project directory should now look like this:
```
.claude/skills/
dexpaprika-api/
SKILL.md
references/
openapi.yml
cli-reference.md
streaming-api.md
```
### Restart your agent
After installing, restart Claude Code (or your AI tool) so it picks up the new skill files.
***
## Verify it works
After restarting, ask your AI agent a DexPaprika question:
```
What blockchain networks does DexPaprika support?
```
The agent should return a list of supported networks (Ethereum, Solana, Base, Arbitrum, Polygon, and more). If it does, the skill is loaded and working.
Try a more specific query:
```
Get the current price of WETH on Ethereum using DexPaprika
```
The agent should call `https://api.dexpaprika.com/networks/ethereum/tokens/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2` and return the price.
***
## Example queries
Once the skill is loaded, your agent knows how to use the full DexPaprika stack. Here are some things you can ask:
### REST API queries
```
Show me the top 5 pools on Ethereum by 24h volume
```
```
Get 30-day OHLCV data for the USDC/ETH pool on Uniswap V3
```
```
Search for PEPE token across all chains
```
### CLI commands
The skill teaches your agent to use `dexpaprika-cli` if it's installed:
```
Use dexpaprika-cli to stream live prices for WETH on Ethereum
```
```
Run dexpaprika-cli to find newly created pools on Solana
```
### Batch pricing
```
Get prices for WETH, USDC, and DAI on Ethereum in a single request
```
### Streaming
```
Write a Python script that streams real-time WETH prices using DexPaprika's SSE endpoint
```
***
## Skills vs MCP server vs Claude Code plugin
| | Agent Skills | Hosted MCP Server | Claude Code Plugin |
| --------------------------- | ----------------------------------------------------------------- | ------------------------------------- | --------------------------------------------- |
| **Install** | `npx skills add` | Add URL to config | `/plugin install` |
| **What it gives the agent** | Full API knowledge (endpoints, CLI, streaming docs) | Live tool calls (17 MCP tools) | MCP tools + agents + skills |
| **Agent makes API calls** | Yes, directly via REST or CLI | No, MCP server handles calls | MCP server handles calls |
| **Works offline** | Yes (skill files are local) | No (needs mcp.dexpaprika.com) | No (needs MCP server) |
| **Works with** | Claude Code (default), Cursor, Cline, and more via `--agent` flag | Claude Desktop, Cursor, VS Code | Claude Code only |
| **Best for** | Any agent that can make HTTP calls or run CLI commands | Quick AI IDE setup with managed tools | Claude Code users who want everything bundled |
You can combine approaches. Install the skill for API knowledge **and** connect the MCP server for tool-based access. They complement each other.
***
## Manage skills
### List installed skills
```bash theme={null}
npx skills list
```
### Remove the skill
```bash theme={null}
npx skills remove dexpaprika-api
```
### Update to latest version
```bash theme={null}
npx skills update
```
***
## Troubleshooting
Restart your AI agent (Claude Code, Cursor, etc.). Skills are discovered at startup. If it still doesn't load, check that the files are in the right location:
```bash theme={null}
ls .claude/skills/dexpaprika-api/
```
You should see `SKILL.md` and a `references/` directory.
The `skills` CLI requires Node.js. Make sure you have Node.js 16+ installed:
```bash theme={null}
node --version
```
If Node.js is installed and it still fails, try running with a fresh npx cache:
```bash theme={null}
npx --yes skills add github.com/coinpaprika/skills/ --skill dexpaprika-api
```
Be specific in your prompt. Instead of "What's the price of ETH?", try "Using the DexPaprika API, get the price of WETH on Ethereum." The agent should recognize the skill context and use the correct endpoints.
Skills must be direct children of `.claude/skills/`. If the files ended up nested differently (e.g., `.claude/skills/skills/dexpaprika-api/`), move the `dexpaprika-api` folder so it sits directly inside `.claude/skills/`. See the directory tree in the [Install](#install) section for the expected layout.
***
## Next steps
Install dexpaprika-cli for direct terminal access to all endpoints
Add MCP server access to Claude Desktop, Cursor, or VS Code
Get the full plugin bundle with MCP tools, agents, and skills
Complete endpoint documentation with interactive playground
***
## Get support
Connect with our community and get real-time support.
Share your experience and help us improve.
### FAQs
Not to start. Keyless requests work at 50,000 credits a month per IP, and [registering a free key](https://console.dexpaprika.com) raises that to 300,000. The skill teaches your agent to call the API directly either way.
Skills give your agent knowledge (API docs, CLI commands, common patterns). MCP gives your agent tools (callable functions that return live data). Skills make your agent smarter about the API; MCP does the calling for it.
Claude Code, Cursor, Cline, OpenCode, Aider, and many others. The `skills` CLI auto-detects your agent, or you can target one explicitly with the `--agent` flag. See the [full list of supported agents](https://github.com/vercel-labs/skills).
Yes. Install the skill for deep API knowledge and connect the MCP server for tool-based access. They complement each other well.
No. The skill files are static reference documents stored locally in your project. Your agent reads them locally. API calls only happen when your agent decides to query the DexPaprika API based on what it learned from the skill.
# Use DexPaprika with the Vercel AI SDK
Source: https://docs.dexpaprika.com/ai-integration/vercel-ai-sdk
Pull all 17 DexPaprika tools into the Vercel AI SDK with createMCPClient and the keyless hosted MCP server, then use them with generateText and streamText. No custom code.
The Vercel AI SDK's MCP client connects to DexPaprika's hosted server and returns all 17 tools as ready-to-use AI SDK tools. Drop them into `generateText` or `streamText`. Keyless, no custom code.
## Install
```bash theme={null}
npm install ai @ai-sdk/mcp @ai-sdk/anthropic
```
The AI SDK moves quickly. These snippets were verified against `ai@7.0.40` and `@ai-sdk/mcp@2.0.18`; pin those versions, or check the current [AI SDK MCP docs](https://ai-sdk.dev) if the API has shifted.
## Connect and get the tools
```ts theme={null}
import { createMCPClient } from '@ai-sdk/mcp';
const mcp = await createMCPClient({
transport: { type: 'http', url: 'https://mcp.dexpaprika.com/streamable-http' },
});
const tools = await mcp.tools(); // all 17 DexPaprika tools
```
## Use them in a generation
```ts theme={null}
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const { text } = await generateText({
model: anthropic('claude-sonnet-4-5'),
tools,
maxSteps: 5,
prompt: 'Find the most liquid WETH pool on ethereum and report its 24h volume.',
});
console.log(text);
await mcp.close();
```
Call `mcp.close()` when you are done so the connection is released. Swap in any AI SDK model provider you prefer.
## Tools and the rationale field
The client returns all 17 DexPaprika tools: networks, pools, tokens, OHLCV, transactions, and cross-chain search. See the [MCP tools reference](/ai-integration/mcp-tools) for the full list.
Every read tool takes a required `rationale` string. It is part of each tool's schema, so a capable model fills it automatically. If a smaller model omits it and errors, add a system instruction telling it to include a short rationale.
## Resources
* [Vercel AI SDK docs](https://ai-sdk.dev)
* [Hosted MCP server](/ai-integration/hosted-mcp-server)
* [MCP tools reference](/ai-integration/mcp-tools)
## FAQs
No. The hosted server is keyless for read access. You still need a key for your model provider (Anthropic, OpenAI, and so on). A free registered key raises the monthly allowance from 50,000 credits to 300,000, and Pro raises it to 5,000,000 at 300 requests a minute; see [rate limits](/knowledge-base/rate-limits).
Streamable HTTP: `https://mcp.dexpaprika.com/streamable-http`. The server also serves SSE if you configure the client for it.
No. `mcp.tools()` discovers all 17 tools from the server and returns them in the AI SDK tool shape.
# Vibe code a crypto dashboard with AI
Source: https://docs.dexpaprika.com/ai-integration/vibe-coding-with-ai
Use Claude Code, Cursor, or any AI-assisted IDE with DexPaprika access to build a crypto dashboard from a natural language prompt. No manual API calls, let the AI do the work.
## What is vibe coding?
Vibe coding means describing what you want in natural language and letting an AI assistant write the code. With DexPaprika connected to your AI tool, the AI can fetch real crypto data while it builds, testing endpoints, checking response shapes, and writing working code from the start.
This tutorial shows the workflow, not a fixed script. Your results will vary based on your AI tool, your prompts, and what you ask for.
***
## Prerequisites
Connect DexPaprika to your AI tool using one of these methods:
| Tool | Setup |
| ------------------ | --------------------------------------------------------------------------------------------- |
| **Claude Code** | `/plugin marketplace add coinpaprika/claude-marketplace` then `/plugin install dexpaprika` |
| **Cursor** | Add MCP server URL `https://mcp.dexpaprika.com/sse` in Settings → Tools & Integrations |
| **VS Code** | Add MCP server via Copilot Chat settings |
| **Claude Desktop** | Add `"dexpaprika": {"url": "https://mcp.dexpaprika.com/sse"}` to `claude_desktop_config.json` |
See the [AI Integration overview](/ai-integration) for detailed setup instructions.
***
## Example 1: "Build me a token price dashboard"
### The prompt
Give your AI a clear, specific prompt:
```
Build a single-page HTML dashboard that shows:
1. The current price of SOL, ETH, and BTC
2. Their 24h volume and price change percentage
3. Auto-refreshes every 30 seconds
Use the DexPaprika API. No frameworks, just vanilla HTML, CSS, and JavaScript.
The SOL address on Solana is So11111111111111111111111111111111111111112.
Use the search endpoint to find ETH and BTC addresses.
```
### What happens
The AI will:
1. Call the DexPaprika search endpoint to find ETH and BTC token addresses and networks
2. Read the token details endpoint to understand the response format
3. Write HTML/CSS/JS that fetches from the DexPaprika REST API
4. Use `summary.price_usd` for the price, `summary.24h.volume_usd` for volume, and `summary.24h.last_price_usd_change` for the percentage change
5. Add a `setInterval` for auto-refresh
### Tips for better results
* **Be specific about data fields.** "Show the 24h price change" is better than "show some stats"
* **Name the tokens and networks.** Don't assume the AI knows every address
* **Mention the API by name.** Say "Use the DexPaprika API" so the AI uses the MCP tools or REST endpoints
* **Start simple, then iterate.** Get a working version first, then ask for styling, charts, or more features
***
## Example 2: "Find me the hottest new pools"
### The prompt
```
Use DexPaprika to find pools created in the last 24 hours on Solana
that have more than $10,000 in daily volume and at least 100 transactions.
Show them in a table sorted by volume, with the pool address, DEX, volume,
and transaction count.
```
### What the AI does
The AI will use the pool search endpoint:
```
GET /networks/solana/pools/search?created_after={24h_ago_timestamp}&volume_usd_24h_min=10000&txns_24h_min=100&order_by=volume_usd_24h&sort=desc
```
Then format the results. It might also follow up with pool detail calls to get token pair names.
***
## Example 3: "Build a price comparison tool"
### The prompt
```
Build a Python script that compares the price of WETH across Ethereum,
Arbitrum, and Base networks. Use DexPaprika batch pricing.
Show the price on each network and highlight the highest and lowest.
```
### What the AI does
1. Finds WETH addresses on each network (via search or by knowing common addresses)
2. Makes three batch pricing calls (one per network)
3. Compares results and formats output
***
## Example 4: "Add a live price ticker"
### The prompt
```
Add a live price ticker to the dashboard that streams real-time prices
for SOL and ETH using the DexPaprika streaming API at
streaming.dexpaprika.com. Use Server-Sent Events.
```
### What the AI does
The AI will use the streaming API:
```javascript theme={null}
const evtSource = new EventSource(
"https://streaming.dexpaprika.com/sse/prices?method=token_price&chain=solana&address=So11111111111111111111111111111111111111112"
);
evtSource.addEventListener("token_price", (event) => {
const data = JSON.parse(event.data);
// data.price is the price as a string; parse as decimal for precision
updatePrice(data.chain, data.address, data.price);
});
```
For multiple tokens, the AI should use the POST endpoint with a JSON array.
***
## Iteration tips
Once you have a working first version, iterate with follow-up prompts:
* "Add a chart showing the last 7 days of price history" (AI will use OHLCV endpoint)
* "Show the top 5 pools for each token" (AI will use pool search with the `token_address` filter)
* "Add error handling for when the API is down"
* "Make it responsive for mobile"
* "Add dark mode"
* "Export the data to CSV"
Each iteration builds on the previous code. The AI already has context about which endpoints to use and what the response formats look like.
***
## Common issues
Be explicit: "Use the DexPaprika API at api.dexpaprika.com" or "Use the DexPaprika MCP tool." If using an IDE integration, make sure the MCP server is connected and showing as active.
Point the AI to specific documentation: "Check the token endpoint response format at docs.dexpaprika.com" or provide the correct field path directly: "The price is at response.summary.price\_usd, not response.price."
The DexPaprika REST API supports CORS, so browser requests should work. The streaming API also supports CORS. If you see CORS errors, check that the URL is correct (https, not http).
If the AI invents endpoints, ground it: "Only use endpoints from the DexPaprika API reference at docs.dexpaprika.com/api-reference/introduction. The available endpoints are: /networks, /search, /networks//pools, /networks//tokens/, /networks//pools//ohlcv, etc."
***
## Next steps
Connect DexPaprika to your AI tool
Standard API workflows to reference in your prompts
Real-time price streaming for live dashboards
Advanced pool screening with the filter endpoint
# Connect DexPaprika MCP to VS Code
Source: https://docs.dexpaprika.com/ai-integration/vscode-ide-integration
Learn how to integrate DexPaprika documentation and the hosted MCP server directly into VS Code for enhanced AI-assisted workflows.
## Integration overview
Add the hosted MCP server to VS Code’s Copilot Chat and index docs for better AI assistance.
## What is VS Code?
Visual Studio Code is a popular open-source editor with rich AI and extension capabilities. With MCP (Model Context Protocol) support in Copilot Chat, VS Code can access external data sources and documentation to provide more intelligent coding assistance.
## Why integrate DexPaprika with VS Code?
* **Real-time crypto data access** - Live market data, pool information, and token prices while coding
* **Enhanced AI assistance** - Copilot Chat can reference DexPaprika data for better suggestions
* **DeFi development workflow** - Build faster with comprehensive blockchain data
* **Documentation context** - Quickly reference our API docs and tutorials while you work
***
## Step 1: Install with one click
Opens VS Code and pre-fills the DexPaprika MCP server (`https://mcp.dexpaprika.com/sse`) in Copilot Chat. Click **Install**, then restart VS Code if prompted.
Prefer to configure it manually? Follow the steps below.
1. The install dialog pre-fills the DexPaprika hosted MCP SSE URL:
```
https://mcp.dexpaprika.com/sse
```
2. Click **Install** to complete the setup
3. Restart VS Code if prompted
1. Open **Copilot Chat** in VS Code
2. Ask: "What are the top liquidity pools on Ethereum?"
3. You should see DexPaprika data being retrieved and displayed
4. Ask about specific networks, tokens, or pools to test further
If the button is not visible, copy this URL into the prompt when VS Code asks for the server address: `https://mcp.dexpaprika.com/sse`.
## Available features
Once integrated, you can access comprehensive DexPaprika functionality in Copilot Chat:
### Real-time data access
* **Network information** - Supported blockchain networks
* **DEX data** - Decentralized exchange information and metrics
* **Pool analytics** - Real-time liquidity pool data, volumes, and fees
* **Token information** - Current prices, market data, and token details
* **Search functionality** - Find tokens, pools, and DEXes across networks
### Documentation context
* **API reference** - Complete endpoint docs and examples
* **SDK guides** - Language-specific integration tutorials
* **Best practices** - Coding patterns and optimization tips
* **Troubleshooting** - Common issues and solutions
***
## Usage examples
### Example 1: Building a DeFi dashboard
```
"Help me create a React component that displays the top 5 liquidity pools
on Ethereum using the DexPaprika API. Include volume, TVL, and price data."
```
### Example 2: Token price monitoring
```
"Build a Python script that monitors SOL token prices across different
DEXes and alerts when there are significant price differences."
```
### Example 3: Pool discovery bot
```
"Create a Node.js application that finds newly created liquidity pools
with high trading volume and sends notifications."
```
***
## Troubleshooting
**Symptoms**: Errors or timeouts when connecting.
**Solutions**:
1. Verify internet connection is stable
2. Check the server URL is exactly: `https://mcp.dexpaprika.com/sse`
3. Restart VS Code completely
4. Remove and re-add the MCP configuration
5. Check VS Code output/logs for MCP-related errors
**Symptoms**: DexPaprika tools do not appear or responses lack data.
**Solutions**:
1. Ensure the server is added and enabled in MCP settings
2. Try re-adding via the **Connect to VS Code** button
3. Ask more specific questions (e.g., network or token names)
4. Restart VS Code
**Symptoms**: Slow responses.
**Solutions**:
1. Check network speed
2. Break complex queries into smaller parts
3. Use specific network/token names in queries
***
## Need Help?
Connect with our community for real-time support and discussions
Contact our team for technical assistance and custom integrations
Share your VS Code + DexPaprika integrations with our community! We love seeing what developers build. [Reach out](mailto:support@coinpaprika.com) to showcase your work.
### FAQs
In the MCP prompt, paste `https://mcp.dexpaprika.com/sse`, confirm, and restart VS Code if needed.
Re‑add the server and retry a scoped prompt (include network or token) to ensure the tools activate.
Check the Output/Logs panel for MCP errors, verify the URL, and restart VS Code; re‑add the server if necessary.
# DexPaprika Pro API - authenticated access for paid plans
Source: https://docs.dexpaprika.com/api-pro/introduction
Authenticated DEX and on-chain data for paid plans: 5,000,000 credits a month, 300 requests a minute, real-time data, dedicated infrastructure, and priority support. API key access on api-pro.dexpaprika.com.
**Just getting started?** The standard API at `api.dexpaprika.com` needs no key to start and is the right place to build first. This page is for paid plans, which authenticate against `api-pro.dexpaprika.com` with an API key. See [rate limits](/knowledge-base/rate-limits) for the credit allowances and [pricing](https://dexpaprika.com/api/pricing) for plans.
## What is the Pro API?
The Pro API serves the same DEX and on-chain data as the standard API, on a separate authenticated host, with a higher credit allowance, real-time data instead of the free tier's 15 second delay, a 99.5% SLA and priority support. Enterprise uses this same API and the same onboarding, with limits raised to whatever your workload needs.
5,000,000 credits a month at 300 requests a minute, against 300,000 at 30 on a free key. Enterprise lifts both to custom levels.
Separate infrastructure ensuring consistent performance and availability
Direct access to our engineering team for technical assistance
Secure access control and usage tracking for your organization
***
## Getting started
### 1. Get your API key
Pro is self-serve. You do not need to talk to anyone to start.
Sign up at [console.dexpaprika.com](https://console.dexpaprika.com). No card
is required to create the account.
Pick Pro on the [pricing page](https://dexpaprika.com/api/pricing) at
$99/month, or $1,032 billed annually. Enterprise is the same API with limits
agreed per contract; that one does start with a conversation.
Your key is under **Keys** in the console. Each account has a single API key,
so this is the one you use everywhere.
Create an account, manage your subscription, and read your usage
Create your API key and track credit usage
Enterprise is the one that goes through us. [Email the team](mailto:support@coinpaprika.com) for custom limits, unmetered streaming and a 99.95% SLA.
Building on the free tier first is the right move: `api.dexpaprika.com` needs
no key at all and serves the same data. Come here when you hit the ceiling.
### 2. Authentication
All Pro API requests require authentication via the `Authorization` header:
```bash cURL theme={null}
curl -X GET "https://api-pro.dexpaprika.com/networks/ethereum/pools/search" \
-H "Content-Type: application/json" \
-H "Authorization: api_YOUR_API_KEY_HERE"
```
```python Python theme={null}
import requests
headers = {
"Content-Type": "application/json",
"Authorization": "api_YOUR_API_KEY_HERE"
}
response = requests.get(
"https://api-pro.dexpaprika.com/networks/ethereum/pools/search",
headers=headers
)
print(response.json())
```
```javascript JavaScript theme={null}
const headers = {
'Content-Type': 'application/json',
'Authorization': 'api_YOUR_API_KEY_HERE'
};
fetch('https://api-pro.dexpaprika.com/networks/ethereum/pools/search', {
headers: headers
})
.then(response => response.json())
.then(data => console.log(data));
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api-pro.dexpaprika.com/networks/ethereum/pools/search", nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "api_YOUR_API_KEY_HERE")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
### 3. API key format
Your API key will follow this format:
```
api_your_personal_api_key
```
Always prefix your key with `api_` when using it in the Authorization header.
***
## Differences from the free API
| Feature | Free API | Pro API |
| ----------------------- | --------------------------------------- | ----------------------------------------------- |
| **Base URL** | `https://api.dexpaprika.com` | `https://api-pro.dexpaprika.com` |
| **Authentication** | None required | API key required |
| **Monthly credits** | 50,000 keyless, 300,000 with a free key | 5,000,000 included, then \$20 per additional 1M |
| **Requests per minute** | 30 | 300 |
| **Max data delay** | 15 seconds | real time |
| **SLA** | best effort | 99.5% |
| **Infrastructure** | Shared | Dedicated |
| **Support** | Community | Priority support |
| **Endpoints** | All endpoints | All endpoints |
Credit detail lives on the [rate limits page](/knowledge-base/rate-limits), and plans on [pricing](https://dexpaprika.com/api/pricing). There are no feature gates: every endpoint, all history and all 36 chains are on the free tier too. The rows above are the whole difference. **Enterprise is this same API with the limits raised for your workload**, so everything below applies unchanged.
All endpoints available in the free API are also available in the Pro API. Simply replace the base URL and add authentication.
***
## Quick start example
Let's fetch Solana SOL token data using the Pro API:
Replace `YOUR_API_KEY_HERE` with your actual API key:
```bash theme={null}
curl -X GET "https://api-pro.dexpaprika.com/networks/solana/tokens/So11111111111111111111111111111111111111112" \
-H "Content-Type: application/json" \
-H "Authorization: api_YOUR_API_KEY_HERE"
```
You'll get the same comprehensive data as the free API:
```json Response [expandable] theme={null}
{
"id": "So11111111111111111111111111111111111111112",
"name": "Wrapped SOL",
"symbol": "SOL",
"chain": "solana",
"decimals": 9,
"total_supply": 0,
"has_image": true,
"summary": {
"price_usd": 165.0363416231933,
"fdv": 21277129298.9034355594499,
"liquidity_usd": 21277129298.9034355594499,
"pools": 12543,
"24h": {
"volume": 18207227.90283451,
"volume_usd": 3172926820.723157,
"sells": 11953376,
"buys": 8641848,
"txns": 20595327,
"buy_usd": 1586463410.3615785,
"sell_usd": 1586463410.3615785,
"last_price_usd_change": 2.5
}
}
}
```
***
## Error handling
### Authentication errors
**Error response:**
```json theme={null}
{
"message": "Forbidden"
}
```
**Solution:** Ensure you're including the `Authorization` header with a valid API key in the format `api_YOUR_KEY`.
**Error response:**
```json theme={null}
{
"message": "Unauthorized"
}
```
**Solution:** Check that your API key follows the correct format: `api_` prefix followed by your key string.
### Other errors
All other error codes match the [standard DexPaprika API](/api-reference/introduction):
* `400 Bad Request` - Invalid parameters
* `402 Payment Required` - monthly credit allowance exhausted. Retrying does not help. Buy a credit pack or enable overage in [console.dexpaprika.com](https://console.dexpaprika.com). See [error handling](/knowledge-base/error-handling#402-payment-required)
* `404 Not Found` - Resource not found
* `429 Too Many Requests` - per-minute rate exceeded. Retry after the `Retry-After` header
* `500 Internal Server Error` - Server error
***
## Available endpoints
The Pro API provides access to all DexPaprika endpoints:
Get detailed token information including price, liquidity, and trading volume
Access liquidity pool data and trading statistics
List all supported blockchain networks
Query swap transactions, adds, and removes
Search across tokens, pools, and DEXes
Retrieve multiple token prices in a single request
View complete API documentation in the [REST API Reference](/api-reference/introduction) section. All examples use the free API URL - simply replace with `api-pro.dexpaprika.com` and add authentication.
***
## Migration guide
Migrating from the free API to Pro API is straightforward:
Replace all instances of:
```
https://api.dexpaprika.com
```
With:
```
https://api-pro.dexpaprika.com
```
Add the `Authorization` header to every request:
```javascript theme={null}
headers: {
'Authorization': 'api_YOUR_API_KEY_HERE'
}
```
Pro raises the per-minute ceiling from 30 to 300, so throttling tuned for the free tier is now far too conservative. Keep the throttle and the 429 backoff, just raise the threshold. On Enterprise, set it to whatever your contract allows.
Make test requests to verify your API key works correctly and you're receiving expected responses.
***
## Best practices
**Never expose your API key in client-side code or public repositories.**
* Store API keys in environment variables or secure vaults (e.g., AWS Secrets Manager, HashiCorp Vault)
* Use `.env` files locally and add them to `.gitignore`
* Rotate keys periodically as part of security best practices
* Never hardcode keys in your application source code
```bash Example .env file theme={null}
DEXPAPRIKA_PRO_API_KEY=api_your_personal_api_key
```
**Track your API usage to optimize performance and identify issues early.**
* Watch credit spend against your allowance in [console.dexpaprika.com](https://console.dexpaprika.com), or call `GET /usage` from your own code
* Log all API requests and responses for debugging
* Set up monitoring dashboards to track request volumes
* Monitor response times and error rates
* Alert on unusual patterns or spikes in traffic
* Review usage patterns to optimize your integration
**Always handle authentication errors gracefully and implement retry logic.**
* Catch and handle 403/401 errors separately from other errors
* Implement exponential backoff for transient failures
* Don't retry on authentication errors - fix the key instead
* Log errors with context for easier troubleshooting
* Provide meaningful error messages to end users
```javascript Example error handling theme={null}
try {
const response = await fetch(url, { headers });
if (response.status === 403) {
console.error('Invalid API key - check your credentials');
// Don't retry - fix the key
return;
}
// Handle other errors with retry logic
} catch (error) {
console.error('Request failed:', error);
}
```
**Reuse HTTP connections to reduce latency and improve throughput.**
* Configure HTTP clients to reuse connections
* Set appropriate connection pool sizes (e.g., 10-50 connections)
* Enable keep-alive headers
* Use persistent connections for high-volume applications
* Monitor connection pool metrics
```python Example with Python requests theme={null}
import requests
from requests.adapters import HTTPAdapter
session = requests.Session()
adapter = HTTPAdapter(pool_connections=20, pool_maxsize=20)
session.mount('https://', adapter)
# Reuse session for all requests
response = session.get(url, headers=headers)
```
***
## Next steps
Explore all available endpoints and their parameters
Real-time token price updates via Server-Sent Events
Learn how to build applications with DexPaprika
Check which tokens and pools are available in our database
***
## Get support
Get priority technical support from our engineering team
Connect with our community for general questions
***
## FAQs
Sign up at [console.dexpaprika.com](https://console.dexpaprika.com), subscribe to Pro, and copy the key from the Keys page. No sales call and no waiting. Enterprise is the exception: those limits are agreed per contract, so email [support@coinpaprika.com](mailto:support@coinpaprika.com).
Yes, you can use both APIs in the same application. The free API requires no authentication, while the Pro API requires your API key.
5,000,000 credits a month included, at up to 300 requests a minute, with \$20 per additional 1M beyond that. One request costs one credit, batch endpoints cost one credit per item, and each streaming update delivered costs one credit. Enterprise raises both numbers to fit your workload. See [rate limits](/knowledge-base/rate-limits).
Regenerate it yourself from the Keys page in the console. Because each account has exactly one key, regenerating replaces the old one immediately, so plan a moment when you can redeploy: every integration using the old key stops working at once. If you cannot get into the account, email [support@coinpaprika.com](mailto:support@coinpaprika.com).
Yes, we can provide custom infrastructure solutions for enterprise customers. Contact our sales team to discuss your specific requirements.
The coverage is identical: same endpoints, same chains, same history, no feature gates. The difference is freshness. The free tier is served with a delay of up to 15 seconds; Pro is real time. Pro also adds dedicated infrastructure, a much higher credit allowance, a 99.5% SLA and priority support.
# Get a list of available dexes on a network.
Source: https://docs.dexpaprika.com/api-reference/dexes/get-a-list-of-available-dexes-on-a-network
/api-reference/openapi.yml get /networks/{network}/dexes
# DexPaprika API reference: free DEX data API for 36 chains
Source: https://docs.dexpaprika.com/api-reference/introduction
Free DEX API: token prices, liquidity pools, OHLCV and swap transactions across 36 blockchains. REST endpoints, no API key needed to start. Start with one curl.
**Need a higher allowance and dedicated infrastructure?** Check out the [Pro API](/api-pro/introduction) for 5,000,000 credits a month, 300 requests a minute, and priority support.
The DexPaprika DEX API provides near real-time data about tokens, liquidity pools, and decentralized exchanges on 36 blockchains. Below you can find the most popular endpoints that you can use to build your own applications:
See also: [Networks](/api-reference/networks/get-a-list-of-available-blockchain-networks),
[Liquidity pool endpoint](/api-reference/pools/get-a-pool-on-a-network),
[Swap transactions](/api-reference/pools/get-transactions-of-a-pool-on-a-network-paging-can-be-used-up-to-100-pages),
[Token data](/api-reference/tokens/get-a-tokens-latest-data-on-a-network)
Pool search:filter pools by volume, liquidity, transactions, and creation date on /networks/\{network}/pools/search. Plus batch token prices in a single request.
### Popular endpoints
Get detailed information about any token on a given network like latest price, liquidity, and trading volume
Access liquidity pool data and trading statistics for a given pool address
List supported networks
Fetch swaps/adds/removes
## Quick start
We will make a GET request to the [Token](/api-reference/tokens/get-a-tokens-latest-data-on-a-network) endpoint in order to get the latest price in USD of TRUMP. All we need is the [network ID](/api-reference/networks/get-a-list-of-available-blockchain-networks) and the token address.
```bash theme={null}
curl -X GET "https://api.dexpaprika.com/networks/solana/tokens/6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN"
```
This will return latest data about TRUMP (TRUMP) on Solana:
```json Response [expandable] theme={null}
{
"id": "6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN",
"name": "OFFICIAL TRUMP",
"symbol": "TRUMP",
"chain": "solana",
"decimals": 6,
"total_supply": 1000000000000000,
"description": "",
"website": "",
"explorer": "",
"added_at": "2025-01-17T23:26:21Z",
"summary": {
"price_usd": 13.14091959123721,
"fdv": 13140919591.23721,
"liquidity_usd": 149423162.71562782,
"24h": {
"volume": 21068441.593753017,
"volume_usd": 281073979.6428536,
"sell": 64806,
"buy": 69661,
"txns": 134467
},
"6h": {
"volume": 3947009.6413350017,
"volume_usd": 51291873.69781224,
"sell": 15636,
"buy": 16434,
"txns": 32070
},
"1h": {
"volume": 412765.81969400006,
"volume_usd": 5443526.242503976,
"sell": 1965,
"buy": 1934,
"txns": 3899
},
"30m": {
"volume": 138223.89740200003,
"volume_usd": 1826042.0489568561,
"sell": 666,
"buy": 942,
"txns": 1608
},
"15m": {
"volume": 102904.73769500002,
"volume_usd": 1356865.0163336615,
"sell": 499,
"buy": 634,
"txns": 1133
},
"5m": {
"volume": 16003.968976,
"volume_usd": 210782.49285099082,
"sell": 77,
"buy": 155,
"txns": 232
}
},
"last_updated": "2025-02-25T13:42:32.093353071Z"
}
```
## Base URL
All API endpoints use the following base URL:
```
https://api.dexpaprika.com/
```
## Common use cases
Here are some popular ways to use our API:
```bash theme={null}
# Get USDC price and trading data
curl -X GET "https://api.dexpaprika.com/networks/solana/tokens/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
```
```bash theme={null}
# Get all pools where SOL is traded
curl -X GET "https://api.dexpaprika.com/networks/solana/pools/search?token_address=So11111111111111111111111111111111111111112"
```
```bash theme={null}
# Search for "Jupiter" token across all networks
curl -X GET "https://api.dexpaprika.com/search?query=jupiter"
```
### FAQs
Not to start. The API answers keyless requests at 50,000 credits a month per IP. [Registering a free key](https://console.dexpaprika.com) raises that to 300,000 and needs no card. See [rate limits](/knowledge-base/rate-limits).
Liquidity pools, swap transactions, token metrics/prices, and network coverage across multiple chains.
Use the Coverage Checker tool or the Networks/Token endpoints to locate addresses.
Collection is near real‑time; responses reflect the latest indexed blocks and events.
## Rate limits
The free tier allows 50,000 credits a month without an API key, or 300,000 with a free key, at 30 requests a minute. [Pro](/api-pro/introduction) includes 5,000,000 credits a month at 300 requests a minute. One request costs one credit and batch endpoints cost one credit per item; there are no compute units or per-endpoint weights.
Need higher limits? Pro is self-serve on [pricing](https://dexpaprika.com/api/pricing). For Enterprise, [contact us](mailto:support@coinpaprika.com).
# Get a list of available blockchain networks.
Source: https://docs.dexpaprika.com/api-reference/networks/get-a-list-of-available-blockchain-networks
/api-reference/openapi.yml get /networks
Retrieve a list of all supported blockchain networks, including metadata
like display names and associated details. Ideal for building dropdowns
or querying supported networks for your application.
# Advanced pool filtering.
Source: https://docs.dexpaprika.com/api-reference/pools/advanced-pool-filtering
/api-reference/openapi.yml get /networks/{network}/pools/filter
Removed. Use `/networks/{network}/pools/search` instead.
# Advanced pool filtering across multiple networks.
Source: https://docs.dexpaprika.com/api-reference/pools/advanced-pool-filtering-across-multiple-networks
/api-reference/openapi.yml get /pools/search
Retrieves a paginated list of pools matching the advanced search criteria across multiple chains.
Supports various filters and cursor-based pagination.
# Advanced pool filtering.
Source: https://docs.dexpaprika.com/api-reference/pools/advanced-pool-filtering-on-a-network
get /networks/{network}/pools/filter
Removed. Use /networks/{network}/pools/search instead.
**This endpoint was removed and now returns `410 Gone`.** Use `GET /networks/{network}/pools/search` instead. It supports the same filters plus working `volume_7d`, `volume_30d`, and `liquidity_usd` ranges.
## Migration
Parameter and response changes from the old filter endpoint:
| Old | New |
| --------------------------------------- | -------------------------------------------------------- |
| path `/networks/{network}/pools/filter` | `/networks/{network}/pools/search` |
| `volume_24h_min` | `volume_usd_24h_min` |
| `sort_by` | `order_by` (e.g. `volume_usd_24h`) |
| `sort_dir` | `sort` (`asc` / `desc`) |
| `page` | `cursor` (pass `next_cursor` from the previous response) |
| response `page_info` | `has_next_page` + `next_cursor` |
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?volume_usd_24h_min=500000&order_by=volume_usd_24h&sort=desc&limit=5"
```
See the [pool filtering tutorial](/tutorials/pool-filtering) for full examples.
# Advanced pool filtering on a specific network.
Source: https://docs.dexpaprika.com/api-reference/pools/advanced-pool-filtering-on-a-specific-network
/api-reference/openapi.yml get /networks/{network}/pools/search
Retrieves a paginated list of pools matching the advanced search criteria.
Supports various filters and cursor-based pagination.
# Get a pool on a network.
Source: https://docs.dexpaprika.com/api-reference/pools/get-a-pool-on-a-network
/api-reference/openapi.yml get /networks/{network}/pools/{pool_address}
Retrieve detailed information about a specific on-chain pool,
including token pairs, current price data, and volume metrics.
# Get OHLCV data for a pool pair.
Source: https://docs.dexpaprika.com/api-reference/pools/get-ohlcv-data-for-a-pool-pair
/api-reference/openapi.yml get /networks/{network}/pools/{pool_address}/ohlcv
Retrieves Open-High-Low-Close-Volume (OHLCV) data for a specific pool,
potentially over a specified time range.
- **start** is **required** to set the beginning of the data window.
- **end** is optional; if omitted, data is returned for the "start" date only.
- **limit** can control how many data points to retrieve (e.g., maximum of 100).
- **interval** defines the granularity (e.g., 1h, 4h, 1d).
- **inverted_price** indicates whether to invert the main price ratio.
# Get top pools.
Source: https://docs.dexpaprika.com/api-reference/pools/get-top-pools
/api-reference/openapi.yml get /pools
Removed. Use `/pools/search` instead.
# Get top pools on a network.
Source: https://docs.dexpaprika.com/api-reference/pools/get-top-pools-on-a-network
/api-reference/openapi.yml get /networks/{network}/pools
Removed. Use `/networks/{network}/pools/search` instead.
# Get top pools.
Source: https://docs.dexpaprika.com/api-reference/pools/get-top-x-pools
get /pools
Removed. Use /pools/search instead.
**This endpoint was removed and now returns `410 Gone`.** Use `GET /pools/search` instead. It searches pools across networks; pass a `chains` filter to scope it (for example `chains=ethereum,base`).
## Migration
| Old | New |
| ------------------------------ | -------------------------------------------------------- |
| path `/pools` | `/pools/search` |
| (per-network only) | optional `chains=ethereum,base,...` |
| `order_by=volume_usd` | `order_by=volume_usd_24h` |
| `page` | `cursor` (pass `next_cursor` from the previous response) |
| response `pools` + `page_info` | `results` + `has_next_page` + `next_cursor` |
```bash theme={null}
curl "https://api.dexpaprika.com/pools/search?chains=ethereum,base&order_by=volume_usd_24h&sort=desc&limit=10"
```
For a single network, use [`GET /networks/{network}/pools/search`](/tutorials/pool-filtering).
# Get top pools on a network.
Source: https://docs.dexpaprika.com/api-reference/pools/get-top-x-pools-on-a-network
get /networks/{network}/pools
Removed. Use /networks/{network}/pools/search instead.
**This endpoint was removed and now returns `410 Gone`.** Use `GET /networks/{network}/pools/search` instead. For top pools, sort by volume: `order_by=volume_usd_24h&sort=desc`.
## Migration
| Old | New |
| -------------------------------- | -------------------------------------------------------- |
| path `/networks/{network}/pools` | `/networks/{network}/pools/search` |
| `order_by=volume_usd` | `order_by=volume_usd_24h` |
| `sort` (`asc`/`desc`) | `sort` (unchanged) |
| `page` | `cursor` (pass `next_cursor` from the previous response) |
| response `pools` + `page_info` | `results` + `has_next_page` + `next_cursor` |
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?order_by=volume_usd_24h&sort=desc&limit=10"
```
See the [pool filtering tutorial](/tutorials/pool-filtering) for full examples.
# Get top x pools on a networks dex
Source: https://docs.dexpaprika.com/api-reference/pools/get-top-x-pools-on-a-networks-dex
get /networks/{network}/dexes/{dex}/pools
Removed. Use /networks/{network}/pools/search with the dex_name filter instead.
**This endpoint was removed and now returns `410 Gone`.** Use `GET /networks/{network}/pools/search` with the `dex_name` query parameter instead. The DEX moved out of the path and into a filter, so one endpoint now covers every DEX on the network.
## Migration
| Old | New |
| -------------------------------------------- | -------------------------------------------------------- |
| path `/networks/{network}/dexes/{dex}/pools` | `/networks/{network}/pools/search` |
| path segment `{dex}` | query param `dex_name` |
| `order_by=volume_usd` | `order_by=volume_usd_24h` |
| `sort` (`asc`/`desc`) | `sort` (unchanged) |
| `page` | `cursor` (pass `next_cursor` from the previous response) |
| response `pools` + `page_info` | `results` + `has_next_page` + `next_cursor` |
| row field `volume_usd` | `volume_usd_24h` |
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?dex_name=curve&order_by=volume_usd_24h&sort=desc&limit=10"
```
`dex_name` takes the DEX id, meaning the `dex_id` field from [`GET /networks/{network}/dexes`](/api-reference/dexes/get-a-list-of-available-dexes-on-a-network), matched case-insensitively. Despite the parameter name, passing that response's `dex_name` field, a human display name such as `Uniswap V3`, returns HTTP 200 with an empty `results` array rather than an error. Pass `dex_id`: it is what the old path segment held, so it is the value already sitting in most existing code.
There is no bare `volume_usd` and no `page_info` on the replacement. Code reading either of those names is broken rather than merely out of date.
The filter also works on the cross-network [`GET /pools/search`](/api-reference/pools/advanced-pool-filtering-across-multiple-networks), which the old endpoint had no equivalent for. Combine it with `chains` to scope the result, for example `?dex_name=uniswap_v3&chains=base`.
See the [pool filtering tutorial](/tutorials/pool-filtering) and [advanced pool filtering](/api-reference/pools/advanced-pool-filtering-on-a-specific-network) for the full parameter list.
# Get transactions of a pool on a network. Paging can be used up to 100 pages.
Source: https://docs.dexpaprika.com/api-reference/pools/get-transactions-of-a-pool-on-a-network-paging-can-be-used-up-to-100-pages
/api-reference/openapi.yml get /networks/{network}/pools/{pool_address}/transactions
# Search for tokens, pools, and DEXes
Source: https://docs.dexpaprika.com/api-reference/search/search-for-tokens-pools-and-dexes
/api-reference/openapi.yml get /search
Allows users to search across multiple entities (tokens, pools, and DEXes)
in a single query. Useful for quickly finding resources by name, symbol, or ID.
# Stream real-time prices for an asset
Source: https://docs.dexpaprika.com/api-reference/stream-real-time-prices-for-an-asset
/streaming/api-streaming.yml get /sse/prices
Establishes an SSE stream for a single token's price using query parameters.
**Emitted events:** `token_price` · `ping` · `warning` · `error`
# Stream real-time prices for an asset (Deprecated)
Source: https://docs.dexpaprika.com/api-reference/stream-real-time-prices-for-an-asset-deprecated
/streaming/api-streaming.yml get /stream
**Deprecated - scheduled for removal in 2027. Use [`GET /sse/prices`](#operation/streamSingleAssetPrice) with `method=token_price` instead.**
Establishes an SSE stream for a single token using the legacy `t_p` event channel. The path `/stream` is an alias for `/sse/prices` with identical parameters.
### Migration Guide
Replace `method=t_p` with `method=token_price` and update your event handler to read `PriceResponse` fields instead of `PriceLegacyResponse` fields:
| Legacy field (`t_p` event) | Current field (`token_price` event) | |---|---| | `a` | `address` | | `c` | `chain` | | `p` | `price` | | `t` | `timestamp` | | `t_p` | `timestamp_price` |
**Emitted events:** `t_p` · `token_price` · `ping` · `warning` · `error`
# Stream real-time prices for multiple assets
Source: https://docs.dexpaprika.com/api-reference/stream-real-time-prices-for-multiple-assets
/streaming/api-streaming.yml post /sse/prices
Establishes a multiplexed SSE stream for up to 25 token price subscriptions in a single connection.
**Emitted events:** `token_price` · `ping` · `warning` · `error`
# Stream real-time prices for multiple assets (Deprecated)
Source: https://docs.dexpaprika.com/api-reference/stream-real-time-prices-for-multiple-assets-deprecated
/streaming/api-streaming.yml post /stream
**Deprecated - scheduled for removal in 2027. Use [`POST /sse/prices`](#operation/streamBatchAssetPrices) with `method: token_price` instead.**
Establishes a multiplexed SSE stream for up to 25 token price subscriptions using the legacy `t_p` event channel. The path `/stream` is an alias for `/sse/prices` with identical parameters.
### Migration Guide
In each request body item, replace `"method": "t_p"` with `"method": "token_price"` and update your event handler to read `PriceResponse` fields instead of `PriceLegacyResponse` fields:
| Legacy field (`t_p` event) | Current field (`token_price` event) | |---|---| | `a` | `address` | | `c` | `chain` | | `p` | `price` | | `t` | `timestamp` | | `t_p` | `timestamp_price` |
**Emitted events:** `t_p` · `token_price` · `ping` · `warning` · `error`
# Stream real-time reserve updates for a single pool or token
Source: https://docs.dexpaprika.com/api-reference/stream-real-time-reserve-updates-for-a-single-pool-or-token
/streaming/api-streaming.yml get /sse/reserves
Establishes an SSE stream for reserve updates on a specific pool or token using query string parameters.
**Supported Channel Events:** - `pool_reserves`: Per-block reserve update for a pool. Schema matches `PoolReservesResponse`. - `token_reserves`: Aggregated reserve update across all pools for a token. Schema matches `TokenReservesResponse`. - `ping`: Infrastructure keep-alive payload (`PingEvent`). - `warning`: System warning notifications (`WarningEvent`). - `error`: Stream-level operational exceptions (`ErrorEvent`).
# Stream real-time reserve updates for multiple pools or tokens
Source: https://docs.dexpaprika.com/api-reference/stream-real-time-reserve-updates-for-multiple-pools-or-tokens
/streaming/api-streaming.yml post /sse/reserves
Establishes a multiplexed SSE stream for reserve updates across multiple pools or tokens within a single connection.
**Stream Constraints:** Maximum payload array length is **25 subscriptions**.
**Supported Channel Events:** - `pool_reserves`: Per-block reserve update for a pool. Schema matches `PoolReservesResponse`. - `token_reserves`: Aggregated reserve update for a token. Schema matches `TokenReservesResponse`. - `ping`: Infrastructure keep-alive payload (`PingEvent`). - `error`: Stream-level operational exceptions (`ErrorEvent`).
# Stream real-time swap transactions for a single pool or token
Source: https://docs.dexpaprika.com/api-reference/stream-real-time-swap-transactions-for-a-single-pool-or-token
/streaming/api-streaming.yml get /sse/transactions
Establishes an SSE stream for swap transactions on a specific pool or token using query string parameters.
**Supported Channel Events:** - `pool`: Swap transactions for a pool. Schema matches `TransactionResponse`. - `token`: Swap transactions involving a token. Schema matches `TransactionResponse`. - `ping`: Infrastructure keep-alive payload (`PingEvent`). - `warning`: System warning notifications (`WarningEvent`). - `error`: Stream-level operational exceptions (`ErrorEvent`).
# Stream real-time swap transactions for multiple pools or tokens
Source: https://docs.dexpaprika.com/api-reference/stream-real-time-swap-transactions-for-multiple-pools-or-tokens
/streaming/api-streaming.yml post /sse/transactions
Establishes a multiplexed SSE stream for swap transactions across multiple pools or tokens within a single connection.
**Stream Constraints:** Maximum payload array length is **25 subscriptions**.
**Supported Channel Events:** - `pool`: Swap transactions for a pool. Schema matches `TransactionResponse`. - `token`: Swap transactions involving a token. Schema matches `TransactionResponse`. - `ping`: Infrastructure keep-alive payload (`PingEvent`). - `error`: Stream-level operational exceptions (`ErrorEvent`).
# Advanced token filtering.
Source: https://docs.dexpaprika.com/api-reference/tokens/advanced-token-filtering
/api-reference/openapi.yml get /networks/{network}/tokens/filter
Removed. Use `/networks/{network}/tokens/search` instead.
# Advanced token filtering across multiple networks.
Source: https://docs.dexpaprika.com/api-reference/tokens/advanced-token-filtering-across-multiple-networks
/api-reference/openapi.yml get /tokens/search
Retrieves a paginated list of tokens matching the advanced search criteria across multiple chains.
Supports various filters and cursor-based pagination.
# Advanced token filtering on a specific network.
Source: https://docs.dexpaprika.com/api-reference/tokens/advanced-token-filtering-on-a-specific-network
/api-reference/openapi.yml get /networks/{network}/tokens/search
Retrieves a paginated list of tokens on a specific network matching the advanced search criteria.
Supports various filters and cursor-based pagination.
# Get a token's latest data on a network.
Source: https://docs.dexpaprika.com/api-reference/tokens/get-a-tokens-latest-data-on-a-network
/api-reference/openapi.yml get /networks/{network}/tokens/{token_address}
Retrieves detailed information about a specific token on the given network,
including latest price, metadata, status, and recent summary metrics such as price changes
and volumes over multiple timeframes.
# Get batched prices for multiple tokens on a network.
Source: https://docs.dexpaprika.com/api-reference/tokens/get-batched-prices-for-multiple-tokens-on-a-network
/api-reference/openapi.yml get /networks/{network}/multi/prices
Retrieves the current USD price for a list of specified token addresses
in a single API call. This is more efficient than making multiple requests
to the single-token detail endpoint when you only need pricing information.
# Get batched prices for multiple tokens on a network.
Source: https://docs.dexpaprika.com/api-reference/tokens/get-batched-token-prices-on-a-network
get /networks/{network}/multi/prices
Fetch current USD prices for up to 10 token addresses in a single request
## Endpoint overview
Fetch current USD prices for multiple token addresses in a single request.
See also: [Token details](/api-reference/tokens/get-a-tokens-latest-data-on-a-network)
### Usage
Pass the `network` path parameter and provide a comma-separated list in the `tokens` query parameter.
```bash theme={null}
curl -X GET "https://api.dexpaprika.com/networks/ethereum/multi/prices?tokens=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2,0xdac17f958d2ee523a2206206994597c13d831ec7" | jq
```
Notes:
* Only tokens with available prices are returned (unknown/unpriced tokens are omitted).
* The order of results is not guaranteed.
* If all provided tokens are invalid or unpriced, the response is an empty array with HTTP 200.
* Duplicate input addresses may produce duplicate entries; dedupe client-side if needed.
* Provide tokens as a comma-separated list in a single `tokens` parameter (explode=false).
* Maximum 10 `tokens` per request. More than 10 will return HTTP 400.
* Each address in the batch costs one credit, so ten tokens in one call cost the same ten credits as ten single-token calls. Batching saves round trips, not credits. See [pricing](https://dexpaprika.com/api/pricing) for what each plan includes.
### Response example
```json Response theme={null}
[
{
"id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"chain": "ethereum",
"price_usd": 4400.5697112921535
},
{
"id": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"chain": "ethereum",
"price_usd": 1.0002529851244053
}
]
```
### FAQs
Repeat the `tokens` query parameter for each address (e.g., `?tokens=0x...&tokens=0x...`).
They are ignored and omitted from the response.
No. Do not rely on response order.
The endpoint returns HTTP 200 with an empty array.
No. Duplicate inputs may yield duplicate entries; dedupe client-side if required.
Yes. Provide a single `tokens` parameter with comma-separated addresses.
Up to 10. Requests with more than 10 tokens return HTTP 400.
# Get top tokens on a network.
Source: https://docs.dexpaprika.com/api-reference/tokens/get-top-tokens-on-a-network
/api-reference/openapi.yml get /networks/{network}/tokens/top
Removed. Use `/networks/{network}/tokens/search` instead.
# Get top X pools for a token.
Source: https://docs.dexpaprika.com/api-reference/tokens/get-top-x-pools-for-a-token
/api-reference/openapi.yml get /networks/{network}/tokens/{token_address}/pools
Removed. Use `/networks/{network}/pools/search` with the `token_address` filter instead.
# Get your plan and current-period credit usage.
Source: https://docs.dexpaprika.com/api-reference/utils/get-your-plan-and-current-period-credit-usage
/api-reference/openapi.yml get /usage
Returns the plan the request was evaluated against and, for requests
authenticated with an API key on `api-pro.dexpaprika.com`, the account
email plus credits used and remaining in the current billing period.
Without an API key (or on the public host, where keys are ignored) the
response carries the plan only; all other fields are omitted.
# Retrieve high-level asset statistics
Source: https://docs.dexpaprika.com/api-reference/utils/retrieve-high-level-asset-statistics
/api-reference/openapi.yml get /stats
Provides a snapshot of the total number of chains, factories, pools,
and tokens tracked by this API. Ideal for overview dashboards or
quick system capacity checks.
# Changelog
Source: https://docs.dexpaprika.com/changelog/changelog
Track all updates and changes to DexPaprika API
This page contains all significant updates, improvements, and bug fixes for the DexPaprika API. We're committed to making our product better with each release. Check our [API reference](/api-reference) for the latest version.
## Removed
* `GET /networks/{network}/dexes/{dex}/pools` has been removed and now returns `410 Gone`. Use `GET /networks/{network}/pools/search` with the `dex_name` query parameter instead. The whole route is gone, so an unknown DEX slug returns the same 410 rather than a 404.
## Migration
* The DEX moves out of the path and into a query parameter: `/networks/ethereum/dexes/curve/pools` becomes `/networks/ethereum/pools/search?dex_name=curve`.
* `dex_name` takes the DEX id, the `dex_id` field from `GET /networks/{network}/dexes`, matched case-insensitively. Passing that response's `dex_name` field instead, a display name like `Uniswap V3`, returns an empty `results` array rather than an error.
* Pagination is cursor-based. Read `has_next_page` and `next_cursor` from the response and pass `next_cursor` back as `cursor`, instead of using `page`. There is no `page_info` object on the replacement.
* Rows arrive in a `results` array rather than `pools`.
* The 24h volume field is `volume_usd_24h`. There is no bare `volume_usd`, and `order_by=volume_usd` is rejected with a `400`, so sort with `order_by=volume_usd_24h`.
* `dex_name` also applies on the cross-network `GET /pools/search`, which the removed endpoint had no equivalent for. Combine it with `chains` to scope the result, for example `?dex_name=uniswap_v3&chains=base`.
## Removed
* `GET /networks/{network}/tokens/{token_address}/pools` has been removed and now returns `410 Gone`. Use `GET /networks/{network}/pools/search` with the new `token_address` query parameter instead.
## Added
* **`token_address` filter on pool search.** `GET /networks/{network}/pools/search` now accepts `token_address` to restrict results to pools that contain the token on the given network. It combines with all existing search filters and sorting (`order_by`, default `volume_usd_24h`).
## Migration
* The filter is network-scoped only. The cross-network `GET /pools/search` accepts `token_address` but silently ignores it, so use the per-network path when filtering by token.
* `token_address` takes a single token. Repeating the parameter does not act as a pair filter; the API uses only one of the values (not guaranteed by order). The old `address` (second-token pair filter) and `reorder` (pair-perspective flip) parameters have no equivalent on pool search.
* Pagination is cursor-based (`has_next_page` + `next_cursor`), rows are in a `results` array, and sorting uses `order_by=volume_usd_24h` instead of the old `order_by=volume_usd`.
* An unknown `token_address` returns HTTP 200 with an empty `results` array, not an error.
## Removed
The legacy pool and token list/filter endpoints have been removed and now return `410 Gone`. Each is replaced by a unified search endpoint:
* `GET /networks/{network}/pools` and `GET /networks/{network}/pools/filter` are replaced by `GET /networks/{network}/pools/search`
* `GET /pools` is replaced by `GET /pools/search`
* `GET /networks/{network}/tokens/filter` and `GET /networks/{network}/tokens/top` are replaced by `GET /networks/{network}/tokens/search`
* Need results across several networks in one call? Use `GET /pools/search` and `GET /tokens/search` with a `chains` filter (for example `chains=ethereum,base,solana`).
## Added
* **Richer filtering on the search endpoints.** Min/max ranges for `volume_usd_24h`, `volume_usd_7d`, `volume_usd_30d`, `liquidity_usd`, `txns_24h`, `price_usd`, and `price_change_percentage_24h`; `created_after` / `created_before`; `dex_name` (pools); `fdv_usd_min` / `fdv_usd_max` (tokens). The `volume_7d`, `volume_30d`, and `liquidity_usd` filters now work, which they did not on the old filter endpoint.
## Migration
* Parameter renames: `volume_24h_min` becomes `volume_usd_24h_min`, `sort_by` becomes `order_by`, `sort_dir` becomes `sort`.
* Pagination is now cursor-based. Read `has_next_page` and `next_cursor` from the response and pass `next_cursor` back as `cursor`, instead of using `page`.
* Response rows are in a `results` array. Each pool row uses `id` (the pool address) and `transactions_24h`.
## Deprecated
* The legacy `/stream` price endpoint is deprecated and scheduled for removal in 2027. Migrate to `/sse/prices`. The `t_p` event is replaced by `token_price`.
## Changed
* **Reserves stream restructure.** The `/sse/reserves` feed no longer emits a single shared `reserve_update` event. Each subscription method now has its own event name, so you match on the method you asked for:
* `method=pool_reserves` emits a `pool_reserves` event with the full pool snapshot. That means the `tokens` array with per-token reserve and delta, plus two new fields: `timestamp` and `block_timestamp`.
* `method=token_reserves` emits a `token_reserves` event flattened to the subscribed token's reserve state in the affected pool, plus two new fields: `updated_at` and `timestamp`.
* If you were matching on `reserve_update`, stop. Switch your handler to the method-named event or you will receive nothing.
## Added
* **`request_id` correlation on streams.** Every SSE endpoint (`/sse/prices` and `/sse/reserves`, both GET and POST) now takes an optional `request_id` query parameter, and the POST body accepts it per asset. The server echoes it back on data events as an SSE `request_id:` line, which is what you want when several subscriptions share one multiplexed POST connection. It is omitted from `ping`, `warning`, and `error` events. The value has to be a non-negative integer (`0` to `4294967295`); send anything non-numeric and the request is rejected with `{"message": "invalid query parameters: request_id (must be a number)"}`. On the POST body the default is the asset's array index.
## Notes
* Stream `error` events always carry a single `{"message": "..."}` payload. That is now stated explicitly in the streaming reference.
Hosted MCP server (`mcp.dexpaprika.com`) refactored end-to-end. All 17 tools now emit `structuredContent` (MCP 2025-06-18+), declare `outputSchema`, and ship with read-only/idempotent annotations so well-behaved clients stop safe-prompting every call.
## Breaking changes
* **`getTokenMultiPrices` response shape.** Previously returned a bare array of price entries; now returns an object `{ prices: [...], missing_tokens: [...] }`. Tokens that upstream cannot price (invalid address, no liquidity, unknown contract) are surfaced in `missing_tokens` rather than silently dropped.
* **Migration:** replace `response.map(...)` with `response.prices.map(...)`. Check `response.missing_tokens.length > 0` to detect partial failures.
* **`search` response `pools[].dex_id` renamed to `factory_id`.** In `search.pools[]`, upstream emits the DEX factory contract address under `dex_id`, while `getNetworkPools` emits the slug under the same key. The MCP layer now renames this to `factory_id` in search responses, matching `getPoolDetails` and `getPoolTransactions` which already use `factory_id` for this concept.
* **Migration:** if you read `search.pools[].dex_id` expecting a slug, read `dex_name` (human-readable) or look up `factory_id` against `getNetworkDexes`.
## Added
* **Network synonym resolution.** Common alternate names (`eth`, `matic`, `avax`, `bnb`, `sol`, `arb`, `op`, `pol`, `cro`, `mnt`, `ftm`, `trx`, etc.) now resolve to their canonical network IDs at the wire layer. `getCapabilities.network_synonyms` advertised these previously; now the upstream actually honors them on every endpoint that takes a `network` parameter.
* **`search` optional `limit` parameter.** Caps items returned per category (tokens, pools, dexes). Applies independently to each category: `limit=3` returns up to 3 hits in each of the 5 categories.
* **`getCapabilities.tools_count`** field exposing the canonical 17-tool count.
* **`getTokenMultiPrices.missing_tokens`** array surfacing tokens upstream could not price.
* **`getPoolOHLCV.interval`** now accepts `1m` and `10m` (in addition to existing `5m`/`15m`/`30m`/`1h`/`6h`/`12h`/`24h`).
* **Canonical alias parameters** added alongside legacy names. Both accepted; canonical is recommended for new code:
* `sort_dir` (canonical) / `sort` (legacy alias): sort direction
* `sort_by` (canonical) / `order_by` (legacy alias): sort field
* `paired_token_address` (canonical) / `address` (legacy alias): second-token filter on `getTokenPools`
* `inversed` (canonical) / `reorder` (legacy alias): pair-perspective flip on `getTokenPools` and `getPoolDetails`
* **`outputSchema`** declared on every tool. Strict-validating MCP clients (Cursor, Claude Desktop) now validate responses against documented shapes.
* **`structuredContent`** emitted on every tool result (MCP spec 2025-06-18+). Agents can consume parsed objects directly instead of re-parsing `content[0].text`.
* **Tool annotations** (`readOnlyHint`, `idempotentHint`, `destructiveHint`, `openWorldHint`) on every tool so hosts stop safe-prompting reads.
* **Server-level `instructions`** field in the MCP `initialize` response, a curated onboarding doc for agents.
## Fixed
* **`getNetworkDexes` `limit` parameter.** Upstream `/networks/{network}/dexes` silently ignored `limit` and always reported `page_info.total_items=0, total_pages=0`. The MCP layer now slices client-side to the requested limit and patches `page_info` to reflect the real total count.
* **`getTokenDetails` strict-validation rejection.** Previously failed on strict-validating MCP clients with `-32602 "data must NOT have additional properties"` when upstream returned fields beyond the declared schema. Outer `outputSchema` is now permissive (`additionalProperties: true`) while declared fields remain typed. Same fix applied across all single-item-object tools (`getTokenDetails`, `getPoolDetails`, etc.).
## Changed
* **`server.version`** bumped from `1.4.0` to `2.0.0`.
* **JSON Schema dialect on `/json-rpc`** transport upgraded from `draft-07` to `2020-12`, aligning with the underlying OpenAPI 3.1 contract. SDK-managed `/sse` and `/streamable-http` transports still emit `draft-07` (Zod v3 limitation); full 2020-12 alignment pending a future Zod v4 Mini migration.
* **Rationale boilerplate** in tool descriptions shortened from 506 to 249 characters to reduce input-token cost on every tool registration.
* **`submitFeedback`** field descriptions now document the 500-character caps on `expected` and `observed` (exceeding triggers `-32602` input validation).
## Known limitations
* `liquidity_usd` values returned by `getNetworkPoolsFilter`, `getTopTokens`, and `getTokenDetails.summary` are currently unreliable upstream: filter results may be empty and reported values may be incorrect. Upstream fix pending.
* `total_supply` decimal handling differs across endpoints (raw units in pool listings, decimal-adjusted in `getTokenDetails`). Upstream consistency pending.
* `getTopTokens` reports `fdv: 0` for some major stablecoins (USDT, DAI). Upstream data quality fix pending.
* Field naming varies across endpoints (`id` vs `address` for token references; `fdv` vs `fdv_usd`; response wrappers `pools`/`results`/`tokens`). Naming alignment is on the roadmap.
## Added
* `GET /networks/{network}/pools/{pool_address}/transactions`: new optional `from` and `to` query parameters (UNIX timestamps, int64) to filter transactions by time range.
* `from` is inclusive, `to` is exclusive. Results are always capped to the last 7 days.
* Token schema: new `telegram` and `twitter` string fields for official social links.
## Added
* New endpoint: `GET /networks/{network}/tokens/filter` for [advanced token filtering](/api-reference/tokens/advanced-token-filtering).
* Filter tokens by volume (`volume_24h`, `volume_7d`, `volume_30d`), liquidity (`liquidity_usd`), FDV (`fdv_min`, `fdv_max`), transaction count (`txns_24h_min`), and creation date (`created_after`, `created_before`).
* Sort by `volume_24h`, `volume_7d`, `volume_30d`, `liquidity_usd`, `txns_24h`, `created_at`, or `fdv`.
* Paginated response with `page_info`.
* New endpoint: `GET /networks/{network}/tokens/top` for [top tokens on a network](/api-reference/tokens/get-top-tokens-on-a-network).
* Ranked token list with enriched metadata and multi-timeframe metrics (24h, 1h, 5m).
* Order by `volume_24h`, `price_usd`, `liquidity_usd`, `txns`, or `price_change`.
* Each token includes buy/sell counts, volume, and price change per interval.
* New schemas: `TokenFilterResponse`, `TokenSearchResult`, `TopTokenResponse`, `TopTokenItem`, `TopTokenTimeData`.
## Changed
* `GET /networks` response schema updated from `Network` to `NetworkMetrics` with new fields: `volume_usd_24h`, `txns_24h`, `pools_count`.
* `GET /networks/{network}/dexes` response enriched with `dex_id`, `volume_usd_24h`, `txns_24h`, `pools_count` per DEX.
* `GET /networks/{network}/pools/filter`: `PoolSearchResult` now extends `PoolWithPrices` via `allOf`, adding `volume_usd_7d` and `liquidity_usd`.
* Pool filter parameters `volume_7d_min`, `liquidity_usd_min`, and `liquidity_usd_max` are now functional.
* New pool filter parameters: `volume_7d_max`, `volume_30d_max`.
## Known limitations
* `volume_30d_min` and `volume_30d_max` parameters on pool and token filter endpoints are documented but not yet functional.
## Added
* New endpoint: `GET /networks/{network}/pools/filter` for [advanced pool filtering](/api-reference/pools/advanced-pool-filtering-on-a-network).
* Filter pools by 24h volume range (`volume_24h_min`, `volume_24h_max`), transaction count (`txns_24h_min`), and creation date (`created_after`, `created_before`).
* Sort by `volume_24h`, `volume_7d`, `volume_30d`, `liquidity`, `txns_24h`, or `created_at` with `asc`/`desc` direction.
* Paginated response with `page_info` including `total_items` and `total_pages`.
* New schemas: `PoolFilterResponse` and `PoolSearchResult`.
## Known limitations
* `volume_7d_min`, `volume_30d_min`, `liquidity_usd_min`, and `liquidity_usd_max` parameters are documented but not yet functional. These will be enabled in a future update.
## Changed
* `GET /networks/{network}/multi/prices`: requests are now limited to a maximum of 10 `tokens` per call.
* Parameter encoding updated to a single comma-separated `tokens` list (`explode=false`).
## Error handling
* Requests with more than 10 tokens return `400 Bad Request`.
* Requests with zero tokens still return `400 Bad Request` (unchanged).
## Docs
* Updated API reference, tutorial, and MCP notes to reflect the cap, edge-case behavior, and comma-separated encoding.
## Added
* New endpoint: `GET /networks/{network}/multi/prices` for batch token price retrieval on a network.
* Request: repeatable `tokens` query parameter (e.g., `?tokens=0x...&tokens=0x...`).
* Response: array of `{ id, chain, price_usd }` objects for tokens with available prices; unknown/unpriced tokens are omitted.
## Changed
* `AssetPrice` schema added and documented in OpenAPI.
* Internals: performance optimization to fetch token summaries conditionally.
## Compatibility
* Certain price-related fields now use `omitempty` semantics in responses. If you previously relied on nulls for absent values, note these keys may now be omitted when data is unavailable.
## Docs
* New API page: [Get batched token prices on a network](/api-reference/tokens/get-batched-token-prices-on-a-network).
* Updated tutorials and references to include batch pricing guidance.
## Enhanced
* **Transaction Schema:**
* Added `token_0_symbol` and `token_1_symbol` fields to transaction objects for explicit token symbol tracking.
* Added `price_0`, `price_1`, `price_0_usd`, and `price_1_usd` fields to transaction objects for detailed price reporting.
* Added `created_at` field to transaction objects for precise transaction timestamping.
## Migration/Compatibility
* No breaking changes to existing endpoints, but clients parsing transaction objects should update their models to support the new fields for full compatibility.
### FAQs
We ship changes continuously. Breaking changes are called out explicitly with migration guidance.
Each breaking change section includes steps and replacement endpoints when applicable.
Watch this changelog and our Discord announcements; major updates are posted there.
## Deprecated
* **BREAKING**: The [`/pools`](/api-reference/pools/get-top-x-pools) endpoint has been permanently deprecated and now returns `410 Gone`
* Users should migrate to [`/networks/{network}/pools`](/api-reference/pools/get-top-x-pools-on-a-network) to get top pools for each specific network
## Changed
* Enhanced deprecation messaging with clear migration paths for affected endpoints
## Migration guide
Instead of using the deprecated global pools endpoint:
```
GET /pools
```
Use the network-specific pools endpoint for each blockchain:
```
GET /networks/ethereum/pools
GET /networks/solana/pools
GET /networks/fantom/pools
```
This change provides better performance and more relevant results by focusing on network-specific data.
## Added
* Introduced the optional `reorder` query parameter to the [`/networks/{network}/tokens/{token_address}/pools`](/api-reference/tokens/get-top-x-pools-for-a-token) endpoint. This allows clients to reorder pool data so the specified token becomes the primary token for all metrics and calculations.
## Added
* Added operation IDs to all endpoints for better client code generation
* Added fully diluted valuation (`fdv`) field to token responses
* Added detailed license information and contact details in API specification
* Added `buy_usd` and `sell_usd` fields to provide monetary values for trades
* Added `last_price_usd_change` field to all time intervals (24h, 6h, 1h, 30m, 15m, 5m, 1m)
* Added organized API tags with descriptions for better navigation
## Changed
* Updated OpenAPI specification from 3.0.3 to 3.1.0 for improved documentation
* Changed [OHLCV endpoint](/api-reference/pools/get-ohlcv-data-for-a-pool-pair) response format from wrapped object to direct array of records (Breaking Change)
* Renamed `buy`/`sell` fields to `buys`/`sells` for consistency (Breaking Change)
* Changed [network](/api-reference/networks) response format from object to array for cleaner consumption
* Updated network schema with improved field naming (`display_name` instead of just `name`)
* Standardized ID field in Network schema to use string identifiers instead of numeric IDs
* Enhanced token schema with additional fields: chain, total\_supply, added\_at, last\_updated
* Improved parameter documentation with examples and clearer descriptions
## Fixed
* Improved consistency in representing null values in responses
* Updated example responses to more accurately reflect actual API behavior
* Fixed formatting inconsistencies in API documentation
## Improved
* Enhanced OHLCV schema with time\_open and time\_close fields to clearly define candlestick periods
* Improved validation for time intervals and limits in OHLCV endpoint
* Added explicit error response documentation for the /stats endpoint
* Enhanced descriptions for all endpoints and parameters
## Added
* Support for buy/sell volume metrics across all time intervals
* Added transaction counts to pool details
* Added initial support for price tracking
## Changed
* Improved error messaging with more specific error codes
* Enhanced documentation with more descriptive examples
## Fixed
* Corrected timestamp format inconsistencies across endpoints
* Fixed incorrect price calculations in some edge cases
## Added
* Initial public beta release
* Support for Solana network
* Basic endpoints for [networks](/api-reference/networks), [DEXes](/api-reference/dexes/get-a-list-of-available-dexes-on-a-network), [pools](/api-reference/pools/get-top-x-pools), and [tokens](/api-reference/tokens/get-a-tokens-latest-data-on-a-network)
* [Search functionality](/api-reference/search/search-for-tokens-pools-and-dexes) across tokens, pools, and DEXes
* OHLCV data for historical price tracking
* Transaction history for pools
## Added
* Pool and token details endpoints
* Initial version of search functionality
## Changed
* Improved error handling and response formats
* Enhanced documentation with examples
## Added
* Initial development release
* Preliminary endpoints for pools and DEXes
# Coverage
Source: https://docs.dexpaprika.com/coverage
Which blockchain networks, DEXes, liquidity pools and tokens the DexPaprika API covers, generated from the live API.
This is the canonical record of what DexPaprika covers. If you are citing our coverage anywhere,
cite this page: it is regenerated from the live API every day rather than written by hand, so it
does not drift.
*Figures below were generated from the live API on 2026-08-12.*
## Networks
Every chain DexPaprika covers, ordered by 24 hour trading volume. The `network` column is the
value the API expects wherever an endpoint takes a network.
| Chain | `network` value | DEXes | Actively traded pools | 24h volume | 24h transactions |
| ------------------- | --------------- | ----: | --------------------: | ---------: | ---------------: |
| Solana | `solana` | 9 | 91,444 | \$4.24B | 22,427,901 |
| Binance Smart Chain | `bsc` | 13 | 31,984 | \$1.46B | 5,237,318 |
| Ethereum | `ethereum` | 19 | 8,880 | \$721.3M | 261,106 |
| Base | `base` | 18 | 12,247 | \$550.2M | 1,163,469 |
| Robinhood Chain | `robinhood` | 3 | 15,700 | \$399.8M | 2,869,685 |
| Arbitrum | `arbitrum` | 17 | 1,491 | \$76.3M | 177,815 |
| Polygon | `polygon` | 10 | 2,296 | \$56.1M | 726,416 |
| Tron | `tron` | 1 | 34 | \$53.1M | 5,709 |
| HyperEVM | `hyperevm` | 10 | 359 | \$41.1M | 62,066 |
| Avalanche | `avalanche` | 8 | 3,523 | \$18.9M | 80,032 |
| Monad | `monad` | 6 | 215 | \$11.5M | 34,373 |
| Sui | `sui` | 3 | 634 | \$10.2M | 125,742 |
| Optimism | `optimism` | 7 | 624 | \$8.5M | 111,917 |
| Plasma | `plasma` | 3 | 29 | \$5.4M | 11,479 |
| x Layer | `x_layer` | 4 | 215 | \$4.8M | 92,588 |
| Sei | `sei` | 1 | 15 | \$3.4M | 5,955 |
| Unichain | `unichain` | 3 | 168 | \$3.2M | 21,122 |
| Aptos | `aptos` | 5 | 148 | \$1.4M | 14,886 |
| Flow EVM | `flow_evm` | 6 | 45 | \$1.2M | 6,587 |
| Linea | `linea` | 8 | 146 | \$1.1M | 5,778 |
| Mantle | `mantle` | 3 | 63 | \$748K | 2,017 |
| Celo | `celo` | 4 | 89 | \$737K | 21,021 |
| Sonic | `sonic` | 13 | 427 | \$541K | 22,495 |
| Berachain | `berachain` | 3 | 130 | \$469K | 7,051 |
| Cronos | `cronos` | 2 | 536 | \$453K | 8,142 |
| Ton | `ton` | 1 | 301 | \$355K | 7,827 |
| Katana | `katana` | 2 | 39 | \$321K | 2,303 |
| Ronin | `ronin` | 2 | 250 | \$223K | 15,295 |
| MegaETH | `megaeth` | 3 | 40 | \$118K | 1,657 |
| ZkSync | `zksync` | 3 | 80 | \$59K | 727 |
| Tempo | `tempo` | 1 | 3 | \$17K | 514 |
| Blast | `blast` | 1 | 31 | \$2K | 612 |
| Scroll | `scroll` | 3 | 19 | \$2K | 213 |
| BOB Network | `bob_network` | 1 | 17 | \$1K | 196 |
| Fantom | `fantom` | 2 | 44 | \$56 | 179 |
| Botanix | `botanix` | 2 | 0 | \$0 | 0 |
## What these numbers mean
The two pool figures count different things, and both are accurate.
**38M+ pools** and **35M+ tokens** are total database coverage:
everything DexPaprika has recorded across all 36 chains.
**Actively traded pools** in the table above counts pools with recent trading activity, which is the
set pool search returns. It is a much smaller and faster moving number, because most pools ever
created stop trading. Indexing is not instantaneous, so a brand new pool reaches the database before
it appears in the active set.
Which one you want depends on the question. For "how much of the DEX market does this cover", use the
totals. For "how many pools can I query on Base right now", use the table.
The DEX columns differ for a similar reason. **235 DEXes** counts every DEX recognised
across all chains, while the table lists the 200 currently
returned by the per-chain DEXes endpoint.
## Check a specific asset
The numbers above answer "how much does DexPaprika cover". To answer "do you have this particular
token or pool", use the [coverage checker](/tools/coverage-checker), which searches the live dataset
by contract address, name or symbol.
## Get these figures yourself
Both endpoints answer keyless requests, so you can run these now and [register a free key](https://console.dexpaprika.com) later for a bigger allowance.
```bash theme={null}
# Platform totals
curl "https://api.dexpaprika.com/stats"
# Every network, with its API value and pool count
curl "https://api.dexpaprika.com/networks"
# DEXes on one network
curl "https://api.dexpaprika.com/networks/ethereum/dexes"
```
Polling `/stats` is also a reasonable liveness check: it costs one credit and reflects the latest successful
aggregation.
## Requesting a chain
If a network you need is missing, tell us at [support@coinpaprika.com](mailto:support@coinpaprika.com)
or in [Discord](https://discord.gg/DhJge5TUGM). Volume and the availability of a reliable node are
what usually decide the order we add chains in, so it helps to say what you are building.
# DexPaprika DEX API Go SDK: on-chain liquidity and swap data client
Source: https://docs.dexpaprika.com/get-started/sdk-go
The official Go client library for the DexPaprika API, providing easy access to decentralized exchange data across multiple blockchain networks
See also: [REST intro](/api-reference/introduction),
[Networks](/api-reference/networks/get-a-list-of-available-blockchain-networks),
[Pools](/api-reference/pools/get-a-pool-on-a-network)
## Installation
```bash theme={null}
go get github.com/coinpaprika/dexpaprika-sdk-go
```
## Prerequisites
* Go 1.24 or higher
* Connection to the internet to access the DexPaprika API
* No API key needed to start
## Quick Example: Get Token Price
```go theme={null}
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/coinpaprika/dexpaprika-sdk-go/dexpaprika"
)
func main() {
// Create client
client := dexpaprika.NewClient()
// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Get WETH token details on Ethereum
token, err := client.Tokens.GetDetails(ctx, "ethereum", "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")
if err != nil {
log.Fatalf("Error getting token details: %v", err)
}
fmt.Printf("%s: $%.2f\n", token.Name, token.PriceUSD)
// Output: Wrapped Ether: $3245.67
}
```
## API Methods Reference
Parameters marked with an asterisk (\*) are required.
### client.Networks.List(ctx)
**Endpoint:** [GET `/networks`](/api-reference/networks/get-a-list-of-available-blockchain-networks)
Gets all supported blockchain networks including Ethereum, Solana, etc.
**Parameters:**
* `ctx`\* - Context for API request
**Returns:** Network IDs, names, and related information. [Response Structure](/api-reference/networks/get-a-list-of-available-blockchain-networks).
```go theme={null}
networks, err := client.Networks.List(ctx)
```
### client.Networks.ListDexes(ctx, networkId, options)
**Endpoint:** [GET `/networks/{network}/dexes`](/api-reference/dexes/get-a-list-of-available-dexes-on-a-network)
Gets all DEXes on a specific network.
**Parameters:**
* `ctx`\* - Context for API request
* `networkId`\* - ID of the network (e.g., 'ethereum', 'solana')
* `options` - ListOptions containing pagination parameters:
* `Page` - Page number for pagination (starts at 0)
* `Limit` - Number of results per page
**Returns:** DEX IDs, names, pool counts, and volume information. [Response Structure](/api-reference/dexes/get-a-list-of-available-dexes-on-a-network).
```go theme={null}
options := &dexpaprika.ListOptions{
Page: 0,
Limit: 10,
}
dexes, err := client.Networks.ListDexes(ctx, "ethereum", options)
```
### client.Pools.List(ctx, options)
**The `GET /pools` endpoint was removed and returns `410 Gone`.** Whatever this
method does locally, the request cannot succeed. Use the network-scoped method
below and pass a network, or call `GET /pools/search` with a `chains` filter
directly. See [pool filtering](/tutorials/pool-filtering).
**Endpoint:** [GET `/pools`](/api-reference/pools/get-top-x-pools) (removed, `410 Gone`)
Gets top pools across all networks with pagination.
**Parameters:**
* `ctx`\* - Context for API request
* `options` - ListOptions containing pagination and sorting parameters:
* `Page` - Page number for pagination (starts at 0)
* `Limit` - Number of results per page
* `Sort` - Sort direction ('asc' or 'desc')
* `OrderBy` - Field to sort by ('volume\_usd', 'liquidity\_usd', etc.)
**Returns:** Paginated list of pool objects with pricing data. [Response Structure](/api-reference/pools/get-top-x-pools).
```go theme={null}
options := &dexpaprika.ListOptions{
Limit: 10,
OrderBy: "volume_usd",
Sort: "desc",
}
pools, err := client.Pools.List(ctx, options)
```
***
### client.Pools.ListByNetwork(ctx, networkId, options)
**Endpoint:** [GET `/networks/{network}/pools/search`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets pools on a specific network with pagination and sorting options.
**Parameters:**
* `ctx`\* - Context for API request
* `networkId`\* - ID of the network
* `options` - `ListOptions`. The endpoint is cursor-paginated, so page forward with `Cursor` taken from the previous response's `NextCursor`; `Page` is not sent. Legacy sort values are mapped, so `OrderBy: "volume_usd"` is sent as `order_by=volume_usd_24h`, and REST rejects the legacy spelling with `400`
**Returns:** Cursor-paginated pools for the given network: a `results` array plus `has_next_page` and `next_cursor`. [Response Structure](/api-reference/pools/advanced-pool-filtering-on-a-specific-network).
```go theme={null}
options := &dexpaprika.ListOptions{
Limit: 5,
OrderBy: "volume_usd_24h",
Sort: "desc",
}
pools, err := client.Pools.ListByNetwork(ctx, "ethereum", options)
```
***
### client.Pools.ListByDex(ctx, networkId, dexId, options)
**`GET /networks/{network}/dexes/{dex}/pools` was removed and returns `410 Gone`.**
This method now calls `GET /networks/{network}/pools/search?dex_name=...` instead. The DEX id
moved out of the path and into a filter, so `Page` is gone and the response shape changed.
See [pool filtering](/tutorials/pool-filtering).
**Endpoint:** [GET `/networks/{network}/pools/search?dex_name=...`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets pools on a specific DEX within a network.
**Parameters:**
* `ctx`\* - Context for API request
* `networkId`\* - ID of the network
* `dexId`\* - ID of the DEX, the `dex_id` field from `client.Networks.ListDexes`, case-insensitive (a display name like `Uniswap V3` returns no rows instead of an error)
* `options` - ListOptions containing `Limit`, `OrderBy`, `Sort`, and `Cursor`
`ListOptions.Page` is not sent for this call any more. Page through with `Cursor`, taking the
value from `NextCursor` on the previous response. The SDK normalizes legacy sort values, so
`volume_usd` goes out as `order_by=volume_usd_24h`; REST rejects the legacy spelling with a
`400` that lists the values it will take.
**Returns:** Cursor-paginated pools for the given DEX: a `results` array plus `has_next_page` and `next_cursor`. [Response Structure](/api-reference/pools/advanced-pool-filtering-on-a-specific-network).
```go theme={null}
options := &dexpaprika.ListOptions{
Limit: 10,
OrderBy: "volume_usd_24h",
Sort: "desc",
}
pools, err := client.Pools.ListByDex(ctx, "ethereum", "uniswap_v3", options)
```
***
### client.Pools.GetDetails(ctx, networkId, poolAddress, options)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}`](/api-reference/pools/get-a-pool-on-a-network)
Gets detailed information about a specific pool.
**Parameters:**
* `ctx`\* - Context for API request
* `networkId`\* - ID of the network
* `poolAddress`\* - On-chain address of the pool
* `options` - PoolDetailOptions containing:
* `Inversed` - Whether to invert the price ratio (boolean)
**Returns:** Detailed pool information including tokens, volumes, liquidity, and more. [Response Structure](/api-reference/pools/get-a-pool-on-a-network).
```go theme={null}
options := &dexpaprika.PoolDetailOptions{
Inversed: false,
}
poolDetails, err := client.Pools.GetDetails(ctx, "ethereum", "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc", options)
```
***
### client.Pools.GetTransactions(ctx, networkId, poolAddress, options)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}/transactions`](/api-reference/pools/get-transactions-of-a-pool-on-a-network-paging-can-be-used-up-to-100-pages)
Gets transaction history for a specific pool with pagination.
**Parameters:**
* `ctx`\* - Context for API request
* `networkId`\* - ID of the network
* `poolAddress`\* - On-chain address of the pool
* `options` - ListOptions containing pagination parameters
**Returns:** List of transactions with details about tokens, amounts, and timestamps. [Response Structure](/api-reference/pools/get-transactions-of-a-pool-on-a-network-paging-can-be-used-up-to-100-pages).
```go theme={null}
options := &dexpaprika.ListOptions{
Limit: 20,
Page: 0,
}
transactions, err := client.Pools.GetTransactions(ctx, "ethereum", "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc", options)
```
***
### client.Pools.GetOHLCV(ctx, networkId, poolAddress, options)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}/ohlcv`](/api-reference/pools/get-ohlcv-data-for-a-pool-pair)
Gets OHLCV (Open, High, Low, Close, Volume) chart data for a pool.
**Parameters:**
* `ctx`\* - Context for API request
* `networkId`\* - ID of the network
* `poolAddress`\* - On-chain address of the pool
* `options` - OHLCVOptions containing:
* `Start`\* - Start time (time.Time or string ISO format)
* `End` - End time (optional)
* `Limit` - Number of data points to return
* `Interval` - Time interval ('1h', '6h', '24h', etc.)
* `Inversed` - Whether to invert the price ratio (boolean)
**Returns:** Array of OHLCV data points for the specified time range and interval. [Response Structure](/api-reference/pools/get-ohlcv-data-for-a-pool-pair).
```go theme={null}
// Start time 7 days ago
startTime := time.Now().AddDate(0, 0, -7)
options := &dexpaprika.OHLCVOptions{
Start: startTime,
Limit: 100,
Interval: "1h",
Inversed: false,
}
ohlcv, err := client.Pools.GetOHLCV(ctx, "ethereum", "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc", options)
```
### client.Tokens.GetDetails(ctx, networkId, tokenAddress)
**Endpoint:** [GET `/networks/{network}/tokens/{token_address}`](/api-reference/tokens/get-a-tokens-latest-data-on-a-network)
Gets comprehensive token information.
**Parameters:**
* `ctx`\* - Context for API request
* `networkId`\* - ID of the network
* `tokenAddress`\* - Token contract address
**Returns:** Token details including price, market cap, volume, and metadata. [Response Structure](/api-reference/tokens/get-a-tokens-latest-data-on-a-network).
```go theme={null}
token, err := client.Tokens.GetDetails(ctx, "ethereum", "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")
```
***
### client.Tokens.GetPools(ctx, networkId, tokenAddress, options)
**Endpoint:** [GET `/networks/{network}/pools/search?token_address=...`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets pools containing a specific token.
**Parameters:**
* `ctx`\* - Context for API request
* `networkId`\* - ID of the network
* `tokenAddress`\* - Token contract address
* `options` - `*TokenPoolsOptions`, which embeds `*ListOptions` for `Limit`, `Sort`, `OrderBy` and `Cursor`. `Page` is not sent upstream: the endpoint is cursor-paginated, so page forward with `Cursor` taken from the previous response's `NextCursor`
* `options.AdditionalTokenAddress` - deprecated and no longer sent. `GET /networks/{network}/pools/search` accepts one `token_address` only, and repeating it makes the API use a single value rather than filtering for the pair. Filter `results[].tokens` yourself if you need a specific pair
* `options.Reorder` - deprecated and no longer sent; the search endpoint has no equivalent
The SDK normalizes legacy sort values before they go on the wire, so `OrderBy: "volume_usd"` is sent as `order_by=volume_usd_24h`. REST rejects the legacy spelling with `400`, so use the canonical value when you call the API directly.
**Returns:** List of pools where the queried token is found. [Response Structure](/api-reference/pools/advanced-pool-filtering-on-a-specific-network).
```go theme={null}
options := &dexpaprika.TokenPoolsOptions{
ListOptions: &dexpaprika.ListOptions{
Limit: 10,
OrderBy: "volume_usd_24h",
Sort: "desc",
},
}
// Get the busiest WETH pools on Ethereum
pools, err := client.Tokens.GetPools(
ctx,
"ethereum",
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH
options,
)
```
### client.Search.Search(ctx, query)
**Endpoint:** [GET `/search`](/api-reference/search/search-for-tokens-pools-and-dexes)
Searches across tokens, pools, and DEXes using a query string.
**Parameters:**
* `ctx`\* - Context for API request
* `query`\* - Search query string
**Returns:** Matching entities from all categories (tokens, pools, DEXes). [Response Structure](/api-reference/search/search-for-tokens-pools-and-dexes).
```go theme={null}
results, err := client.Search.Search(ctx, "ethereum")
```
### client.Utils.GetStats(ctx)
**Endpoint:** [GET `/stats`](/api-reference/utils/retrieve-high-level-asset-statistics)
Gets platform-wide statistics.
**Parameters:**
* `ctx`\* - Context for API request
**Returns:** Counts of chains, DEXes, pools, and tokens indexed. [Response Structure](/api-reference/utils/retrieve-high-level-asset-statistics).
```go theme={null}
stats, err := client.Utils.GetStats(ctx)
```
## Complete Example
```go theme={null}
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/coinpaprika/dexpaprika-sdk-go/dexpaprika"
)
func main() {
// Initialize client
client := dexpaprika.NewClient()
// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Get Ethereum network details
networks, err := client.Networks.List(ctx)
if err != nil {
log.Fatalf("Error fetching networks: %v", err)
}
var ethereum *dexpaprika.Network
for _, network := range networks {
if network.ID == "ethereum" {
ethereum = &network
break
}
}
if ethereum == nil {
log.Fatal("Ethereum network not found")
}
fmt.Printf("Found %s with %d DEXes\n", ethereum.DisplayName, ethereum.DexesCount)
// Get WETH token details
weth, err := client.Tokens.GetDetails(
ctx,
"ethereum",
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
)
if err != nil {
log.Fatalf("Error fetching token details: %v", err)
}
fmt.Printf("%s price: $%.2f\n", weth.Name, weth.PriceUSD)
// Find the busiest WETH pools
options := &dexpaprika.TokenPoolsOptions{
ListOptions: &dexpaprika.ListOptions{
Limit: 5,
OrderBy: "volume_usd_24h",
Sort: "desc",
},
}
pools, err := client.Tokens.GetPools(
ctx,
"ethereum",
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH
options,
)
if err != nil {
log.Fatalf("Error fetching pools: %v", err)
}
// Show top pools. /pools/search rows carry VolumeUSD24h, not VolumeUSD
fmt.Println("Top WETH pools:")
for _, pool := range pools.Pools {
var vol float64
if pool.VolumeUSD24h != nil {
vol = *pool.VolumeUSD24h
}
fmt.Printf("%s: $%.2f 24h volume\n", pool.DexName, vol)
}
}
```
## Advanced Features
### Error Handling
```go theme={null}
import (
"context"
"errors"
"fmt"
"log"
"time"
"github.com/coinpaprika/dexpaprika-sdk-go/dexpaprika"
)
func handleAPIErrors() {
client := dexpaprika.NewClient()
ctx := context.Background()
// Attempt to get a token with an invalid address
_, err := client.Tokens.GetDetails(ctx, "ethereum", "0xinvalidaddress")
if err != nil {
// Check for specific error types
var apiErr *dexpaprika.APIError
if errors.As(err, &apiErr) {
switch apiErr.StatusCode {
case 404:
fmt.Println("Token not found")
case 429:
fmt.Println("Rate limit exceeded, retry after a delay")
case 500:
fmt.Println("Server error, retry may succeed")
default:
fmt.Printf("API error: %s\n", apiErr.Message)
}
} else {
// Handle non-API errors (like network issues)
fmt.Printf("Non-API error: %v\n", err)
}
// Check if error is retryable
if dexpaprika.IsRetryable(err) {
fmt.Println("This error is retryable")
}
}
}
```
### Caching
```go theme={null}
import (
"context"
"fmt"
"time"
"github.com/coinpaprika/dexpaprika-sdk-go/dexpaprika"
)
func useCaching() {
// Create a regular client
client := dexpaprika.NewClient()
// Create a cached client with 5-minute TTL
cachedClient := dexpaprika.NewCachedClient(client, nil, 5*time.Minute)
// Create context
ctx := context.Background()
// First call - hits the API
startTime := time.Now()
networks, err := cachedClient.GetNetworks(ctx)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("First call (API): %d networks, took %v\n", len(networks), time.Since(startTime))
// Second call - served from cache (much faster)
startTime = time.Now()
networks, err = cachedClient.GetNetworks(ctx)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Second call (cached): %d networks, took %v\n", len(networks), time.Since(startTime))
}
```
### Pagination Helpers
```go theme={null}
import (
"context"
"fmt"
"time"
"github.com/coinpaprika/dexpaprika-sdk-go/dexpaprika"
)
func usePagination() {
client := dexpaprika.NewClient()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Create a paginator for Ethereum pools with 50 items per page
options := &dexpaprika.ListOptions{
Limit: 50,
OrderBy: "volume_usd_24h",
Sort: "desc",
}
paginator := dexpaprika.NewPoolsPaginator(client, options).ForNetwork("ethereum")
// Count total pools processed
totalPools := 0
// Process first 3 pages (or fewer if there aren't that many)
for i := 0; i < 3 && paginator.HasNextPage(); i++ {
// Get next page of results
if err := paginator.GetNextPage(ctx); err != nil {
fmt.Printf("Error getting page: %v\n", err)
break
}
// Process current page
pools := paginator.GetCurrentPage()
totalPools += len(pools)
// Process first few pools in each page
fmt.Printf("=== Page %d ===\n", i+1)
for j, pool := range pools {
if j >= 3 {
fmt.Printf("...and %d more pools\n", len(pools)-3)
break
}
var vol float64
if pool.VolumeUSD24h != nil {
vol = *pool.VolumeUSD24h
}
fmt.Printf("%s: $%.2f 24h volume\n", pool.DexName, vol)
}
}
fmt.Printf("Processed %d pools total\n", totalPools)
}
```
### Custom Configuration
```go theme={null}
import (
"net/http"
"time"
"github.com/coinpaprika/dexpaprika-sdk-go/dexpaprika"
)
func configureClient() *dexpaprika.Client {
// Create a client with custom configuration
client := dexpaprika.NewClient(
// Custom HTTP client with longer timeout
dexpaprika.WithHTTPClient(&http.Client{
Timeout: 60 * time.Second,
}),
// Custom retry configuration (5 retries with backoff)
dexpaprika.WithRetryConfig(5, 2*time.Second, 30*time.Second),
// Rate limiting to 3 requests per second
dexpaprika.WithRateLimit(3.0),
// Custom user agent
dexpaprika.WithUserAgent("MyApp/1.0 DexPaprikaClient"),
)
return client
}
```
## Resources
* [GitHub Repository](https://github.com/coinpaprika/dexpaprika-sdk-go)
* [DexPaprika Website](https://dexpaprika.com)
* [API Reference](/api-reference/introduction)
## API Status
The DexPaprika API provides consistent data with stable endpoints. No API key is currently required to access the service. We aim to maintain backward compatibility and provide notice of any significant changes.
### FAQs
Not to start. Keyless requests work at 50,000 credits a month per IP, and a [free registered key](https://console.dexpaprika.com/dashboard) raises that to 300,000 with no card.
Use Coverage Checker or list Networks and query Tokens/Pools to discover addresses.
Use pools/transactions endpoints with `pool_address`, `network`, and time/paging params as documented.
30 requests a minute on the free tier, 300 on Pro, against a monthly allowance of 50,000 credits keyless, 300,000 with a free key, or 5,000,000 on Pro. Retry transient HTTP errors with backoff. See [rate limits](/knowledge-base/rate-limits) for how the counters work, and [Pro pricing](https://dexpaprika.com/api/pricing) for what the 5,000,000 credit tier costs.
# DexPaprika DEX API PHP SDK: on-chain liquidity and swap data client
Source: https://docs.dexpaprika.com/get-started/sdk-php
The official PHP client library for the DexPaprika API, providing easy access to decentralized exchange data across multiple blockchain networks
See also: [REST intro](/api-reference/introduction),
[Networks](/api-reference/networks/get-a-list-of-available-blockchain-networks),
[Pools](/api-reference/pools/get-a-pool-on-a-network)
## Installation
```bash theme={null}
# Using Composer
composer require coinpaprika/dexpaprika-sdk-php
# From source
git clone https://github.com/coinpaprika/dexpaprika-sdk-php
cd dexpaprika-sdk-php
composer install
```
## Prerequisites
* PHP 7.4 or higher
* [Composer](https://getcomposer.org/)
* `ext-json` PHP extension
* Connection to the internet to access the DexPaprika API
* No API key needed to start
## PHP SDK Quickstart
```php theme={null}
tokens->getTokenDetails('ethereum', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2');
echo "{$weth['name']}: \${$weth['price_usd']}";
// Output: Wrapped Ether: $3245.67
```
## API Methods Reference
Parameters marked with an asterisk (\*) are required.
### client->networks->getNetworks()
**Endpoint:** [GET `/networks`](/api-reference/networks/get-a-list-of-available-blockchain-networks)
Gets all supported blockchain networks including Ethereum, Solana, etc.
**Parameters:** None
**Returns:** Network IDs, names, and related information. [Response Structure](/api-reference/networks/get-a-list-of-available-blockchain-networks).
```php theme={null}
// Get all networks
$networks = $client->networks->getNetworks();
echo "Found {$networks['networks']} networks";
```
### client->networks->findNetwork(networkId)
Gets a network by its ID.
**Parameters:**
* `networkId`\* - ID of the network to find (e.g., 'ethereum')
**Returns:** The network information or null if not found.
```php theme={null}
// Find Ethereum network
$ethereum = $client->networks->findNetwork('ethereum');
if ($ethereum) {
echo "Found network: {$ethereum['display_name']}";
}
```
### client->networks->getNetworkDexes(networkId, options)
**Endpoint:** [GET `/networks/{network}/dexes`](/api-reference/dexes/get-a-list-of-available-dexes-on-a-network)
Gets all DEXes on a specific network.
**Parameters:**
* `networkId`\* - ID of the network (e.g., 'ethereum', 'solana')
* `options` - Additional options:
* `page` - Page number for pagination (starts at 0)
* `limit` - Number of results per page
**Returns:** DEX IDs, names, pool counts, and volume information. [Response Structure](/api-reference/dexes/get-a-list-of-available-dexes-on-a-network).
```php theme={null}
// Get all DEXes on Ethereum
$dexes = $client->networks->getNetworkDexes('ethereum', ['limit' => 10]);
echo "Found {$dexes['dexes']} DEXes on Ethereum";
```
### client->networks->findDex(networkId, dexId)
Finds a specific DEX on a network by its ID.
**Parameters:**
* `networkId`\* - ID of the network
* `dexId`\* - ID of the DEX to find
**Returns:** The DEX information or null if not found.
```php theme={null}
// Find Uniswap V3 on Ethereum
$dex = $client->networks->findDex('ethereum', 'uniswap-v3');
if ($dex) {
echo "Found DEX: {$dex['name']}";
}
```
### client->pools->getTopPools(options)
**The `GET /pools` endpoint was removed and returns `410 Gone`.** Whatever this
method does locally, the request cannot succeed. Use the network-scoped method
below and pass a network, or call `GET /pools/search` with a `chains` filter
directly. See [pool filtering](/tutorials/pool-filtering).
**Endpoint:** [GET `/pools`](/api-reference/pools/get-top-x-pools) (removed, `410 Gone`)
Gets top pools across all networks with pagination.
**Parameters:**
* `options` - Additional options:
* `page` - Page number for pagination (starts at 0)
* `limit` - Number of results per page
* `orderBy` - Field to sort by ('volume\_usd', 'liquidity\_usd', etc.)
* `sort` - Sort direction ('asc' or 'desc')
**Returns:** Paginated list of pool objects with pricing data. [Response Structure](/api-reference/pools/get-top-x-pools).
```php theme={null}
// Get top 10 pools by volume
$topPools = $client->pools->getTopPools([
'limit' => 10,
'orderBy' => 'volume_usd',
'sort' => 'desc'
]);
echo "Top pool: {$topPools['pools'][0]['dex_name']}";
```
### client->pools->getNetworkPools(networkId, options)
**Endpoint:** [GET `/networks/{network}/pools/search`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets pools on a specific network with pagination and sorting options.
**Parameters:**
* `networkId`\* - ID of the network
* `options` - Additional options:
* `limit` - Number of results per page
* `orderBy` - Field to sort by ('volume\_usd\_24h', 'liquidity\_usd', 'txns\_24h', and the rest of the canonical list). Legacy values are mapped, so `volume_usd` is sent as `order_by=volume_usd_24h`, and REST rejects the legacy spelling with `400`
* `sort` - Sort direction ('asc' or 'desc')
* `cursor` - Cursor for the next page, taken from `next_cursor` on the previous response. There is no `page` option: the endpoint is cursor-paginated
**Returns:** Cursor-paginated pools for the given network: a `results` array plus `has_next_page` and `next_cursor`. [Response Structure](/api-reference/pools/advanced-pool-filtering-on-a-specific-network).
```php theme={null}
// Get top 5 pools on Ethereum by volume
$pools = $client->pools->getNetworkPools('ethereum', [
'limit' => 5,
'orderBy' => 'volume_usd_24h',
'sort' => 'desc'
]);
echo "Found " . count($pools['results']) . " pools on Ethereum";
```
### client->pools->getDexPools(networkId, dexId, options)
**`GET /networks/{network}/dexes/{dex}/pools` was removed and returns `410 Gone`.**
This method now calls `GET /networks/{network}/pools/search?dex_name=...` instead. The DEX id
moved out of the path and into a filter, so `page` is gone and the response shape changed.
See [pool filtering](/tutorials/pool-filtering).
**Endpoint:** [GET `/networks/{network}/pools/search?dex_name=...`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets pools on a specific DEX within a network.
**Parameters:**
* `networkId`\* - ID of the network
* `dexId`\* - ID of the DEX, the `dex_id` field from `getNetworkDexes`, case-insensitive (a display name like `Uniswap V3` returns no rows instead of an error)
* `options` - Additional options:
* `limit` - Number of results per page (max 100)
* `orderBy` - Field to sort by ('volume\_usd\_24h', 'liquidity\_usd', 'txns\_24h', and the rest of the canonical list)
* `sort` - Sort direction ('asc' or 'desc')
* `cursor` - Cursor for the next page, taken from `next_cursor` on the previous response
There is no `page` option any more. The SDK normalizes legacy sort values, so `volume_usd` is
sent as `order_by=volume_usd_24h`; REST rejects the legacy spelling with a `400` that lists the
values it will take.
**Returns:** A `results` array plus `has_next_page` and `next_cursor` for cursor pagination. [Response Structure](/api-reference/pools/advanced-pool-filtering-on-a-specific-network).
```php theme={null}
// Get the busiest Uniswap V3 pools on Ethereum
$uniswapPools = $client->pools->getDexPools('ethereum', 'uniswap_v3', [
'limit' => 10,
'orderBy' => 'volume_usd_24h',
'sort' => 'desc'
]);
foreach ($uniswapPools['results'] as $pool) {
echo "{$pool['dex_name']}: $" . number_format($pool['volume_usd_24h'], 2) . " 24h volume\n";
}
```
### client->pools->getPoolDetails(networkId, poolAddress, options)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}`](/api-reference/pools/get-a-pool-on-a-network)
Gets detailed information about a specific pool.
**Parameters:**
* `networkId`\* - ID of the network
* `poolAddress`\* - On-chain address of the pool
* `options` - Additional options:
* `inversed` - Whether to invert the price ratio (boolean)
**Returns:** Detailed pool information including tokens, volumes, liquidity, and more. [Response Structure](/api-reference/pools/get-a-pool-on-a-network).
```php theme={null}
// Get details for a specific pool (WETH/USDC on Uniswap V2)
$pool = $client->pools->getPoolDetails(
'ethereum',
'0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc',
['inversed' => false]
);
echo "Pool: {$pool['tokens'][0]['symbol']}/{$pool['tokens'][1]['symbol']}";
```
### client->pools->getPoolTransactions(networkId, poolAddress, options)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}/transactions`](/api-reference/pools/get-transactions-of-a-pool-on-a-network-paging-can-be-used-up-to-100-pages)
Gets transaction history for a specific pool with pagination.
**Parameters:**
* `networkId`\* - ID of the network
* `poolAddress`\* - On-chain address of the pool
* `options` - Additional options:
* `page` - Page number for pagination
* `limit` - Number of transactions per page
* `cursor` - Transaction ID used for cursor-based pagination
**Returns:** List of transactions with details about tokens, amounts, and timestamps. [Response Structure](/api-reference/pools/get-transactions-of-a-pool-on-a-network-paging-can-be-used-up-to-100-pages).
```php theme={null}
// Get the latest 20 transactions for a pool
$transactions = $client->pools->getPoolTransactions(
'ethereum',
'0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc',
['limit' => 20]
);
$latestTxTime = date('Y-m-d H:i:s', $transactions['transactions'][0]['block_timestamp']);
echo "Latest transaction: {$latestTxTime}";
```
### client->pools->getPoolOHLCV(networkId, poolAddress, start, options)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}/ohlcv`](/api-reference/pools/get-ohlcv-data-for-a-pool-pair)
Gets OHLCV (Open, High, Low, Close, Volume) chart data for a pool.
**Parameters:**
* `networkId`\* - ID of the network
* `poolAddress`\* - On-chain address of the pool
* `start`\* - Start time (ISO date string, YYYY-MM-DD, or Unix timestamp)
* `options` - Additional options:
* `end` - End time (optional)
* `limit` - Number of data points to return
* `interval` - Time interval ('1m', '5m', '15m', '30m', '1h', '6h', '12h', '24h')
* `inversed` - Whether to invert the price ratio (boolean)
**Returns:** Array of OHLCV data points for the specified time range and interval. [Response Structure](/api-reference/pools/get-ohlcv-data-for-a-pool-pair).
```php theme={null}
// Get OHLCV data for the past 7 days with 1-hour intervals
$endDate = date('Y-m-d');
$startDate = date('Y-m-d', strtotime('-7 days'));
$ohlcv = $client->pools->getPoolOHLCV(
'ethereum',
'0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc',
$startDate,
[
'end' => $endDate,
'interval' => '1h',
'limit' => 168 // 24 * 7 hours
]
);
echo "Received {$ohlcv} OHLCV data points";
```
### client->tokens->getTokenDetails(networkId, tokenAddress)
**Endpoint:** [GET `/networks/{network}/tokens/{token_address}`](/api-reference/tokens/get-a-tokens-latest-data-on-a-network)
Gets comprehensive token information.
**Parameters:**
* `networkId`\* - ID of the network
* `tokenAddress`\* - Token contract address
**Returns:** Token details including price, market cap, volume, and metadata. [Response Structure](/api-reference/tokens/get-a-tokens-latest-data-on-a-network).
```php theme={null}
// Get WETH token details
$weth = $client->tokens->getTokenDetails(
'ethereum',
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
);
echo "{$weth['name']} price: \${$weth['price_usd']}";
```
### client->tokens->getTokenPools(networkId, tokenAddress, options)
**`GET /networks/{network}/tokens/{token_address}/pools` was removed and returns `410 Gone`.**
This method now calls `GET /networks/{network}/pools/search?token_address=...` instead. The old
second-token `address` pair filter and the `reorder` flag have no replacement and are not sent.
See [pool filtering](/tutorials/pool-filtering).
**Endpoint:** [GET `/networks/{network}/pools/search?token_address=...`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets pools containing a specific token.
**Parameters:**
* `networkId`\* - ID of the network
* `tokenAddress`\* - Token contract address
* `options` - Additional options:
* `limit` - Number of results per page (max 100)
* `sort` - Sort direction ('asc' or 'desc')
* `orderBy` - Field to sort by ('volume\_usd\_24h', 'liquidity\_usd', 'txns\_24h', and the rest of the canonical list)
* `cursor` - Cursor for the next page, taken from `next_cursor` on the previous response
There is no `page` option and no second-token `address` option. The endpoint is cursor-paginated,
and `GET /networks/{network}/pools/search` has no pair filter, so anything you pass under those
names is dropped before the request goes out. Filter `results[].tokens` yourself to match a pair.
The SDK also normalizes legacy sort values, so `volume_usd` is sent as `order_by=volume_usd_24h`;
REST rejects the legacy spelling with `400`.
**Returns:** A `results` array plus `has_next_page` and `next_cursor` for cursor pagination. [Response Structure](/api-reference/pools/advanced-pool-filtering-on-a-specific-network).
```php theme={null}
// Get the busiest WETH pools on Ethereum
$pools = $client->tokens->getTokenPools(
'ethereum',
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH
[
'limit' => 5,
'orderBy' => 'volume_usd_24h',
'sort' => 'desc'
]
);
foreach ($pools['results'] as $pool) {
echo "{$pool['dex_name']}: $" . number_format($pool['volume_usd_24h'], 2) . " 24h volume\n";
}
```
### client->tokens->findToken(networkId, tokenAddress)
**Endpoint:** [GET `/networks/{network}/tokens/{token_address}`](/api-reference/tokens/get-a-tokens-latest-data-on-a-network)
Same call as `getTokenDetails`, wrapped so a missing token raises
`NotFoundException` instead of handing you an empty payload to check.
**Parameters:**
* `networkId`\* - ID of the network
* `tokenAddress`\* - Contract address of the token
* `asObject` - Return an object instead of an array (default: `false`)
**Returns:** Token details, the same shape as `getTokenDetails`.
```php theme={null}
// Find WETH by its contract address
try {
$token = $client->tokens->findToken(
'ethereum',
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
);
echo "Found {$token['name']} at {$token['id']}";
} catch (DexPaprika\Exception\NotFoundException $e) {
echo "Token not found";
}
```
### client->search->search(query)
**Endpoint:** [GET `/search`](/api-reference/search/search-for-tokens-pools-and-dexes)
Searches across tokens, pools, and DEXes using a query string.
**Parameters:**
* `query`\* - Search query string
**Returns:** Matching entities from all categories (tokens, pools, DEXes). [Response Structure](/api-reference/search/search-for-tokens-pools-and-dexes).
```php theme={null}
// Search for "ethereum" across all entities
$results = $client->search->search("ethereum");
echo "Found {$results['summary']['total_tokens']} tokens\n";
echo "Found {$results['summary']['total_pools']} pools\n";
echo "Found {$results['summary']['total_dexes']} dexes\n";
```
### client->stats->getStats()
**Endpoint:** [GET `/stats`](/api-reference/utils/retrieve-high-level-asset-statistics)
Gets platform-wide statistics.
**Parameters:** None
**Returns:** Counts of chains, DEXes, pools, and tokens indexed. [Response Structure](/api-reference/utils/retrieve-high-level-asset-statistics).
```php theme={null}
// Get platform statistics
$stats = $client->stats->getStats();
echo "Total chains: {$stats['chains']}\n";
echo "Total DEXes: {$stats['dexes']}\n";
echo "Total pools: {$stats['pools']}\n";
echo "Total tokens: {$stats['tokens']}\n";
```
## Complete Example
```php theme={null}
networks->getNetworks();
$ethereum = null;
foreach ($networks['networks'] as $network) {
if ($network['id'] === 'ethereum') {
$ethereum = $network;
break;
}
}
echo "Found {$ethereum['display_name']} with {$ethereum['dexes_count']} DEXes\n";
// Get WETH token details
$weth = $client->tokens->getTokenDetails(
'ethereum',
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
);
echo "{$weth['name']} price: \${$weth['price_usd']}\n";
// Find the busiest WETH pools
$pools = $client->tokens->getTokenPools(
'ethereum',
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH
[
'limit' => 5,
'orderBy' => 'volume_usd_24h',
'sort' => 'desc'
]
);
// Show top pools
echo "Top WETH pools:\n";
foreach ($pools['results'] as $pool) {
echo "{$pool['dex_name']}: $" . number_format($pool['volume_usd_24h'], 2) . " 24h volume\n";
}
} catch (DexPaprikaApiException $e) {
echo "API Error: {$e->getMessage()}\n";
} catch (Exception $e) {
echo "Error: {$e->getMessage()}\n";
}
}
main();
```
## Advanced Features
### Error Handling
```php theme={null}
tokens->getTokenDetails('ethereum', '0xinvalidaddress');
} catch (NotFoundException $e) {
echo "Token not found: {$e->getMessage()}\n";
} catch (RateLimitException $e) {
echo "Rate limit exceeded. Try again in: {$e->getRetryAfter()} seconds\n";
} catch (ServerException $e) {
echo "Server error occurred: {$e->getMessage()}\n";
} catch (NetworkException $e) {
echo "Network error: {$e->getMessage()}\n";
} catch (DexPaprikaApiException $e) {
echo "API error: {$e->getMessage()} (Code: {$e->getCode()})\n";
} catch (Exception $e) {
echo "General error: {$e->getMessage()}\n";
}
// The SDK automatically retries on these status codes:
// 408 (Request Timeout), 429 (Too Many Requests),
// 500, 502, 503, 504 (Server Errors)
// Custom retry configuration
use DexPaprika\Config;
$config = new Config();
$config->setMaxRetries(4); // Number of retry attempts (default: 3)
$config->setRetryDelays([100, 500, 1000, 5000]); // Delay in milliseconds
$client = new Client(null, null, false, $config);
// All API requests will now use these retry settings
try {
$networks = $client->networks->getNetworks();
} catch (Exception $e) {
echo "Failed after multiple retries: {$e->getMessage()}\n";
}
```
### Caching System
```php theme={null}
setupCache();
// First call - hits the API
$startTime = microtime(true);
$networks = $client->networks->getNetworks();
$firstCallTime = microtime(true) - $startTime;
echo "First call (API): {$networks['networks']} networks, took {$firstCallTime} s\n";
// Second call - served from cache (much faster)
$startTime = microtime(true);
$networks = $client->networks->getNetworks();
$secondCallTime = microtime(true) - $startTime;
echo "Second call (cached): {$networks['networks']} networks, took {$secondCallTime} s\n";
echo "Cache speedup: " . ($firstCallTime / $secondCallTime) . "x\n";
// You can skip the cache when you need fresh data
$client->getConfig()->setCacheEnabled(false);
$freshNetworks = $client->networks->getNetworks();
$client->getConfig()->setCacheEnabled(true);
// Custom cache configuration
$cache = new FilesystemCache('/path/to/custom/cache');
$client->setupCache($cache, 300); // 5 minutes TTL
// Clear the entire cache
$client->getConfig()->getCache()->clear();
```
### Pagination Helper
```php theme={null}
$limit,
'sort' => 'desc',
'orderBy' => 'volume_usd_24h'
];
if ($cursor !== null) {
$options['cursor'] = $cursor;
}
$response = $client->pools->getNetworkPools($networkId, $options);
$allPools = array_merge($allPools, $response['results']);
// The search endpoint is cursor-paginated, not page-numbered
if (empty($response['has_next_page']) || empty($response['next_cursor'])) {
break;
}
$cursor = $response['next_cursor'];
// Free allows 30 requests a minute, so pause 2 seconds between pages
sleep(2);
}
echo "Fetched a total of " . count($allPools) . " pools on {$networkId}\n";
return $allPools;
}
// Alternative approach using built-in paginator
function fetchAllPoolsWithPaginator($networkId) {
$client = new Client();
$allPools = [];
// Create a paginator for network pools
$paginator = $client->createPaginator(
$client->pools,
'getNetworkPools',
[
$networkId,
[
'limit' => 50,
'orderBy' => 'volume_usd_24h',
'sort' => 'desc'
]
]
);
// Iterate through all pages (or up to a maximum)
$paginator->setMaxPages(10); // Optional: limit to 10 pages
foreach ($paginator as $page => $response) {
$allPools = array_merge($allPools, $response['pools']);
echo "Processed page {$page}, got {$response['pools']} pools\n";
}
echo "Fetched a total of {$allPools} pools on {$networkId}\n";
return $allPools;
}
// Usage example
$ethereumPools = fetchAllPools('ethereum');
// or
$ethereumPools = fetchAllPoolsWithPaginator('ethereum');
```
### Working with Objects
```php theme={null}
setResponseFormat('object');
$client = new Client(null, null, true, $config);
// Get pool details
$pool = $client->pools->getPoolDetails(
'ethereum',
'0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640' // USDC/WETH Uniswap v3 pool
);
// Access pool properties as object properties
echo "Pool: {$pool->tokens[0]->symbol}/{$pool->tokens[1]->symbol}\n";
echo "Volume (24h): \${$pool->day->volume_usd}\n";
echo "Transactions (24h): {$pool->day->txns}\n";
echo "Price: \${$pool->last_price_usd}\n";
// Time interval data is available for multiple timeframes
echo "1h price change: {$pool->hour1->last_price_usd_change}%\n";
echo "24h price change: {$pool->day->last_price_usd_change}%\n";
// Combining with array-based response for specific API calls
$client->getConfig()->setResponseFormat('array');
$networks = $client->networks->getNetworks();
// Back to object mode
$client->getConfig()->setResponseFormat('object');
$token = $client->tokens->getTokenDetails('ethereum', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2');
echo "{$token->name} price: \${$token->price_usd}\n";
```
## Resources
* [GitHub Repository](https://github.com/coinpaprika/dexpaprika-sdk-php)
* [DexPaprika Website](https://dexpaprika.com)
* [API Reference](/api-reference/introduction)
* [Discord Community](https://discord.gg/DhJge5TUGM)
## API Status
The DexPaprika API provides consistent data with stable endpoints. No API key is currently required to access the service. We aim to maintain backward compatibility and provide notice of any significant changes.
### FAQs
Not to start. Keyless requests work at 50,000 credits a month per IP, and a [free registered key](https://console.dexpaprika.com/dashboard) raises that to 300,000 with no card.
Use Coverage Checker or list Networks and query Tokens/Pools to discover addresses.
Use pools/transactions endpoints with `pool_address`, `network`, and time/paging params as documented.
30 requests a minute on the free tier, 300 on Pro, against a monthly allowance of 50,000 credits keyless, 300,000 with a free key, or 5,000,000 on Pro. Retry transient HTTP errors with backoff. See [rate limits](/knowledge-base/rate-limits) for how the counters work, and [Pro pricing](https://dexpaprika.com/api/pricing) for what the 5,000,000 credit tier costs.
# DexPaprika DEX API Python SDK: on-chain liquidity and swap data client
Source: https://docs.dexpaprika.com/get-started/sdk-python
The official Python client library for the DexPaprika API, providing easy access to decentralized exchange data across multiple blockchain networks
See also: [REST intro](/api-reference/introduction),
[Networks](/api-reference/networks/get-a-list-of-available-blockchain-networks),
[Pools](/api-reference/pools/get-a-pool-on-a-network)
## Installation
```bash theme={null}
# Using pip
pip install dexpaprika-sdk
# Using poetry
poetry add dexpaprika-sdk
# From source
git clone https://github.com/coinpaprika/dexpaprika-sdk-python
cd dexpaprika-sdk-python
pip install -e .
```
## Prerequisites
* Python 3.8 or higher
* Connection to the internet to access the DexPaprika API
* No API key needed to start
## Quick Example: Get Token Price
```python theme={null}
from dexpaprika_sdk import DexPaprikaClient
from dexpaprika_sdk.models import TokenDetails # Type hint example
# Create client and get WETH price on Ethereum
client = DexPaprikaClient()
token: TokenDetails = client.tokens.get_details("ethereum", "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")
print(f"{token.name}: ${token.price_usd}")
# Output: Wrapped Ether: $3245.67
```
## API Methods Reference
Parameters marked with an asterisk (\*) are required.
### client.networks.list()
**Endpoint:** [GET `/networks`](/api-reference/networks/get-a-list-of-available-blockchain-networks)
Gets all supported blockchain networks including Ethereum, Solana, etc.
**Parameters:** None
**Returns:** Network IDs, names, and related information. [Response Structure](/api-reference/networks/get-a-list-of-available-blockchain-networks).
```python theme={null}
# Get all networks
networks = client.networks.list()
print(f"Found {len(networks)} networks")
```
### client.dexes.list\_by\_network(network\_id, page, limit)
**Endpoint:** [GET `/networks/{network}/dexes`](/api-reference/dexes/get-a-list-of-available-dexes-on-a-network)
Gets all DEXes on a specific network.
**Parameters:**
* `network_id`\* - ID of the network (e.g., 'ethereum', 'solana')
* `page` - Page number for pagination (starts at 0)
* `limit` - Number of results per page
**Returns:** DEX IDs, names, pool counts, and volume information. [Response Structure](/api-reference/dexes/get-a-list-of-available-dexes-on-a-network).
```python theme={null}
# Get all DEXes on Ethereum
dexes = client.dexes.list_by_network("ethereum")
print(f"Found {len(dexes.dexes)} DEXes on Ethereum")
```
### client.pools.list(page, limit, sort, order\_by)
**The `GET /pools` endpoint was removed and returns `410 Gone`.** Whatever this
method does locally, the request cannot succeed. Use the network-scoped method
below and pass a network, or call `GET /pools/search` with a `chains` filter
directly. See [pool filtering](/tutorials/pool-filtering).
**Endpoint:** [GET `/pools`](/api-reference/pools/get-top-x-pools) (removed, `410 Gone`)
Gets top pools across all networks with pagination.
**Parameters:**
* `page` - Page number for pagination (starts at 0)
* `limit` - Number of results per page
* `sort` - Sort direction ('asc' or 'desc')
* `order_by` - Field to sort by ('volume\_usd', 'liquidity\_usd', etc.)
**Returns:** Paginated list of pool objects with pricing data. [Response Structure](/api-reference/pools/get-top-x-pools).
```python theme={null}
# Get top 10 pools by volume
top_pools = client.pools.list(limit=10, order_by="volume_usd", sort="desc")
print(f"Top pool: {top_pools.pools[0].dex_name}")
```
***
### client.pools.list\_by\_network(network\_id, page, limit, sort, order\_by)
**Endpoint:** [GET `/networks/{network}/pools/search`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets pools on a specific network with pagination and sorting options.
**Parameters:**
* `network_id`\* - ID of the network
* `limit` - Number of results per page
* `sort` - Sort direction ('asc' or 'desc')
* `order_by` - Field to sort by ('volume\_usd\_24h', 'liquidity\_usd', 'txns\_24h', and the rest of the canonical list). Legacy values are mapped, so `volume_usd` is sent as `order_by=volume_usd_24h`, and REST rejects the legacy spelling with `400`
* `cursor` - Cursor for the next page, taken from `next_cursor` on the previous response. `page` is validated but never sent: the endpoint is cursor-paginated
**Returns:** Cursor-paginated pools for the given network: a `results` array plus `has_next_page` and `next_cursor`. [Response Structure](/api-reference/pools/advanced-pool-filtering-on-a-specific-network).
```python theme={null}
# Get top 5 pools on Ethereum by volume
pools = client.pools.list_by_network(
network_id="ethereum",
limit=5,
order_by="volume_usd_24h",
sort="desc"
)
print(f"Found {len(pools.results)} pools on Ethereum")
```
***
### client.pools.list\_by\_dex(network\_id, dex\_id, limit, sort, order\_by, cursor)
**`GET /networks/{network}/dexes/{dex}/pools` was removed and returns `410 Gone`.**
This method now calls `GET /networks/{network}/pools/search?dex_name=...` instead. The DEX id
moved out of the path and into a filter, so `page` is gone and the response shape changed.
See [pool filtering](/tutorials/pool-filtering).
**Endpoint:** [GET `/networks/{network}/pools/search?dex_name=...`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets pools on a specific DEX within a network.
**Parameters:**
* `network_id`\* - ID of the network
* `dex_id`\* - ID of the DEX, the `dex_id` field from `client.dexes.list_by_network()`, case-insensitive (a display name like `Uniswap V3` returns no rows instead of an error)
* `limit` - Number of results per page (max 100)
* `sort` - Sort direction ('asc' or 'desc')
* `order_by` - Field to sort by ('volume\_usd\_24h', 'liquidity\_usd', 'txns\_24h', and the rest of the canonical list)
* `cursor` - Cursor for the next page, taken from `next_cursor` on the previous response
There is no `page` argument any more. The SDK normalizes legacy sort values, so `volume_usd` is
sent as `order_by=volume_usd_24h`; REST rejects the legacy spelling with a `400` that lists the
values it will take.
**Returns:** A `results` list plus `has_next_page` and `next_cursor` for cursor pagination. [Response Structure](/api-reference/pools/advanced-pool-filtering-on-a-specific-network).
```python theme={null}
# Get the busiest Uniswap V3 pools on Ethereum
uniswap_pools = client.pools.list_by_dex(
network_id="ethereum",
dex_id="uniswap_v3",
limit=10,
order_by="volume_usd_24h",
sort="desc"
)
for pool in uniswap_pools.results:
print(f"{pool.dex_name}: ${pool.volume_usd_24h:,.2f} 24h volume")
```
***
### client.pools.get\_details(network\_id, pool\_address, inversed)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}`](/api-reference/pools/get-a-pool-on-a-network)
Gets detailed information about a specific pool.
**Parameters:**
* `network_id`\* - ID of the network
* `pool_address`\* - On-chain address of the pool
* `inversed` - Whether to invert the price ratio (boolean)
**Returns:** Detailed pool information including tokens, volumes, liquidity, and more. [Response Structure](/api-reference/pools/get-a-pool-on-a-network).
```python theme={null}
from dexpaprika_sdk.models import PoolDetails # Type hint example
# Get details for a specific pool (WETH/USDC on Uniswap V2)
pool: PoolDetails = client.pools.get_details(
network_id="ethereum",
pool_address="0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc",
inversed=False
)
print(f"Pool: {pool.tokens[0].symbol}/{pool.tokens[1].symbol}")
```
***
### client.pools.get\_transactions(network\_id, pool\_address, page, limit)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}/transactions`](/api-reference/pools/get-transactions-of-a-pool-on-a-network-paging-can-be-used-up-to-100-pages)
Gets transaction history for a specific pool with pagination.
**Parameters:**
* `network_id`\* - ID of the network
* `pool_address`\* - On-chain address of the pool
* `page` - Page number for pagination
* `limit` - Number of transactions per page
**Returns:** List of transactions with details about tokens, amounts, and timestamps. [Response Structure](/api-reference/pools/get-transactions-of-a-pool-on-a-network-paging-can-be-used-up-to-100-pages).
```python theme={null}
# Get the latest 20 transactions for a pool
transactions = client.pools.get_transactions(
network_id="ethereum",
pool_address="0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc",
limit=20
)
from datetime import datetime
latest_tx_time = datetime.fromtimestamp(transactions.transactions[0].block_timestamp).strftime('%Y-%m-%d %H:%M:%S')
print(f"Latest transaction: {latest_tx_time}")
```
***
### client.pools.get\_ohlcv(network\_id, pool\_address, start, end, limit, interval, inversed)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}/ohlcv`](/api-reference/pools/get-ohlcv-data-for-a-pool-pair)
Gets OHLCV (Open, High, Low, Close, Volume) chart data for a pool.
**Parameters:**
* `network_id`\* - ID of the network
* `pool_address`\* - On-chain address of the pool
* `start`\* - Start time (ISO date string, YYYY-MM-DD, or Unix timestamp)
* `end` - End time (optional)
* `limit` - Number of data points to return
* `interval` - Time interval ('1m', '5m', '15m', '30m', '1h', '6h', '12h', '24h')
* `inversed` - Whether to invert the price ratio (boolean)
**Returns:** Array of OHLCV data points for the specified time range and interval. [Response Structure](/api-reference/pools/get-ohlcv-data-for-a-pool-pair).
```python theme={null}
from datetime import datetime, timedelta
# Get OHLCV data for the past 7 days with 1-hour intervals
end_date = datetime.now()
start_date = end_date - timedelta(days=7)
ohlcv = client.pools.get_ohlcv(
network_id="ethereum",
pool_address="0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc",
start=start_date.strftime("%Y-%m-%d"),
end=end_date.strftime("%Y-%m-%d"),
interval="1h",
limit=168 # 24 * 7 hours
)
print(f"Received {len(ohlcv)} OHLCV data points")
```
### client.tokens.get\_details(network\_id, token\_address)
**Endpoint:** [GET `/networks/{network}/tokens/{token_address}`](/api-reference/tokens/get-a-tokens-latest-data-on-a-network)
Gets comprehensive token information.
**Parameters:**
* `network_id`\* - ID of the network
* `token_address`\* - Token contract address
**Returns:** Token details including price, market cap, volume, and metadata. [Response Structure](/api-reference/tokens/get-a-tokens-latest-data-on-a-network).
```python theme={null}
# Get WETH token details
weth = client.tokens.get_details(
network_id="ethereum",
token_address="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
)
print(f"{weth.name} price: ${weth.price_usd}")
```
***
### client.tokens.get\_pools(network\_id, token\_address, limit, sort, order\_by, cursor)
**`GET /networks/{network}/tokens/{token_address}/pools` was removed and returns `410 Gone`.**
This method now calls `GET /networks/{network}/pools/search?token_address=...` instead, so it still
works. The old second-token `address` pair filter and the `reorder` flag have no replacement: pass
either and the SDK raises a `DeprecationWarning` and drops it. See [pool filtering](/tutorials/pool-filtering).
**Endpoint:** [GET `/networks/{network}/pools/search?token_address=...`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets pools containing a specific token.
**Parameters:**
* `network_id`\* - ID of the network
* `token_address`\* - Token contract address
* `limit` - Number of results per page (1 to 100)
* `sort` - Sort direction ('asc' or 'desc')
* `order_by` - Field to sort by ('volume\_usd\_24h', 'liquidity\_usd', 'txns\_24h', and the rest of the canonical list). Legacy values are mapped, so `volume_usd` is sent as `order_by=volume_usd_24h`; REST rejects the legacy spelling with `400`
* `cursor` - Cursor for the next page, taken from `next_cursor` on the previous response. `page` is validated but never sent: the endpoint is cursor-paginated
* `address`, `reorder` - deprecated and ignored
**Returns:** A `PoolSearchResponse` with `results`, `has_next_page` and `next_cursor`. [Response Structure](/api-reference/pools/advanced-pool-filtering-on-a-specific-network).
```python theme={null}
# Get the busiest WETH pools on Ethereum
pools = client.tokens.get_pools(
network_id="ethereum",
token_address="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", # WETH
limit=5,
order_by="volume_usd_24h",
sort="desc",
)
for pool in pools.results:
print(f"{pool.dex_name}: ${pool.volume_usd_24h:,.2f} 24h volume")
```
### client.search.search(query)
**Endpoint:** [GET `/search`](/api-reference/search/search-for-tokens-pools-and-dexes)
Searches across tokens, pools, and DEXes using a query string.
**Parameters:**
* `query`\* - Search query string
**Returns:** Matching entities from all categories (tokens, pools, DEXes). [Response Structure](/api-reference/search/search-for-tokens-pools-and-dexes).
```python theme={null}
# Search for "ethereum" across all entities
results = client.search.search("ethereum")
print(f"Found {len(results.tokens)} tokens")
print(f"Found {len(results.pools)} pools")
print(f"Found {len(results.dexes)} dexes")
```
### client.utils.get\_stats()
**Endpoint:** [GET `/stats`](/api-reference/utils/retrieve-high-level-asset-statistics)
Gets platform-wide statistics.
**Parameters:** None
**Returns:** Counts of chains, DEXes, pools, and tokens indexed. [Response Structure](/api-reference/utils/retrieve-high-level-asset-statistics).
```python theme={null}
# Get platform statistics
stats = client.utils.get_stats()
print(f"Total chains: {stats.chains}")
print(f"Total DEXes: {stats.dexes}")
print(f"Total pools: {stats.pools}")
print(f"Total tokens: {stats.tokens}")
```
## Complete Example
```python theme={null}
from dexpaprika_sdk import DexPaprikaClient
def main():
# Initialize client
client = DexPaprikaClient()
# Get Ethereum network details
networks = client.networks.list()
ethereum = next((n for n in networks if n.id == "ethereum"), None)
print(f"Found {ethereum.display_name} with {ethereum.dexes_count} DEXes")
# Get WETH token details
weth = client.tokens.get_details(
network_id="ethereum",
token_address="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
)
print(f"{weth.name} price: ${weth.price_usd}")
# Find WETH/USDC pools
pools = client.tokens.get_pools(
network_id="ethereum",
token_address="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", # WETH
limit=5,
order_by="volume_usd",
sort="desc",
address="0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" # USDC
)
# Show top pools
print('Top WETH/USDC pools:')
for pool in pools.pools:
print(f"{pool.dex_name}: ${pool.volume_usd:,.2f} 24h volume")
if __name__ == "__main__":
main()
```
## Advanced Features
### Error Handling
```python theme={null}
from dexpaprika_sdk import DexPaprikaClient
import requests
# Basic error handling
try:
client = DexPaprikaClient()
token = client.tokens.get_details("ethereum", "0xinvalidaddress")
except Exception as e:
if "404" in str(e):
print("Token not found")
elif "429" in str(e):
print("Rate limit exceeded")
else:
print(f"An error occurred: {e}")
# The SDK automatically retries on these status codes:
# 408 (Request Timeout), 429 (Too Many Requests),
# 500, 502, 503, 504 (Server Errors)
# Custom retry configuration
client = DexPaprikaClient(
max_retries=4, # Number of retry attempts (default: 4)
backoff_times=[0.1, 0.5, 1.0, 5.0] # Backoff times in seconds
)
# All API requests will now use these retry settings
try:
networks = client.networks.list()
except requests.exceptions.RetryError as e:
print(f"Failed after multiple retries: {e}")
```
### Caching System
```python theme={null}
from dexpaprika_sdk import DexPaprikaClient
import time
# The SDK includes built-in caching by default
# Demonstration of cache behavior
# Create a regular client with default cache (5 minutes TTL)
client = DexPaprikaClient()
# First call - hits the API
start_time = time.time()
networks = client.networks.list()
first_call_time = time.time() - start_time
print(f"First call (API): {len(networks)} networks, took {first_call_time:.4f}s")
# Second call - served from cache (much faster)
start_time = time.time()
networks = client.networks.list()
second_call_time = time.time() - start_time
print(f"Second call (cached): {len(networks)} networks, took {second_call_time:.4f}s")
print(f"Cache speedup: {first_call_time / second_call_time:.1f}x")
# You can skip the cache when you need fresh data
fresh_networks = client.networks._get("/networks", skip_cache=True)
# Clear the entire cache
client.clear_cache()
# Clear cache only for specific endpoints
client.clear_cache(endpoint_prefix="/networks")
# Different types of data have different cache durations:
# - Network data: 24 hours
# - Pool data: 5 minutes
# - Token data: 10 minutes
# - Statistics: 15 minutes
# - Other data: 5 minutes (default)
```
### Pagination Helper
```python theme={null}
from dexpaprika_sdk import DexPaprikaClient
import time
def fetch_all_pools(network_id):
client = DexPaprikaClient()
all_pools = []
limit = 50
cursor = None
while True:
response = client.pools.list_by_network(
network_id=network_id,
limit=limit,
sort="desc",
order_by="volume_usd_24h",
cursor=cursor,
)
all_pools.extend(response.results)
# The search endpoint is cursor-paginated, not page-numbered
if not response.has_next_page or not response.next_cursor:
break
cursor = response.next_cursor
# Free allows 30 requests a minute, so pause 2 seconds between pages
time.sleep(2)
print(f"Fetched a total of {len(all_pools)} pools on {network_id}")
return all_pools
# Usage example
ethereum_pools = fetch_all_pools("ethereum")
```
### Parameter Validation
```python theme={null}
from dexpaprika_sdk import DexPaprikaClient
# The SDK automatically validates parameters before making API requests
client = DexPaprikaClient()
# Invalid parameter examples will raise helpful error messages
try:
# Invalid network ID
client.pools.list_by_network(network_id="", limit=5)
except ValueError as e:
print(e) # "network_id is required"
try:
# Invalid sort parameter
client.pools.list(sort="invalid_sort")
except ValueError as e:
print(e) # "sort must be one of: asc, desc"
try:
# Invalid limit parameter
client.pools.list(limit=500)
except ValueError as e:
print(e) # "limit must be at most 100"
```
### Working with Models
```python theme={null}
from dexpaprika_sdk import DexPaprikaClient
from dexpaprika_sdk.models import PoolDetails # Type hint example
client = DexPaprikaClient()
# Get pool details
pool: PoolDetails = client.pools.get_details(
network_id="ethereum",
pool_address="0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640" # USDC/WETH Uniswap v3 pool
)
# Access pool properties with type checking and auto-completion
print(f"Pool: {pool.tokens[0].symbol}/{pool.tokens[1].symbol}")
print(f"Volume (24h): ${pool.day.volume_usd:.2f}")
print(f"Transactions (24h): {pool.day.txns}")
print(f"Price: ${pool.last_price_usd:.4f}")
# Time interval data is available for multiple timeframes
print(f"1h price change: {pool.hour1.last_price_usd_change:.2f}%")
print(f"24h price change: {pool.day.last_price_usd_change:.2f}%")
# All API responses are converted to typed Pydantic models
# This provides automatic validation, serialization/deserialization,
# and IDE auto-completion support through Python type hints
```
## Resources
* [GitHub Repository](https://github.com/coinpaprika/dexpaprika-sdk-python)
* [DexPaprika Website](https://dexpaprika.com)
* [API Reference](/api-reference/introduction)
* [Discord Community](https://discord.gg/DhJge5TUGM)
## API Status
The DexPaprika API provides consistent data with stable endpoints. No API key is currently required to access the service. We aim to maintain backward compatibility and provide notice of any significant changes.
### FAQs
Not to start. Keyless requests work at 50,000 credits a month per IP, and a [free registered key](https://console.dexpaprika.com/dashboard) raises that to 300,000 with no card.
Use Coverage Checker or list Networks and query Tokens/Pools to discover addresses.
Use pools/transactions endpoints with `pool_address`, `network`, and time/paging params as documented.
30 requests a minute on the free tier, 300 on Pro, against a monthly allowance of 50,000 credits keyless, 300,000 with a free key, or 5,000,000 on Pro. Retry transient HTTP errors with backoff. See [rate limits](/knowledge-base/rate-limits) for how the counters work, and [Pro pricing](https://dexpaprika.com/api/pricing) for what the 5,000,000 credit tier costs.
# DexPaprika DEX API TypeScript SDK: on-chain liquidity and swap data client
Source: https://docs.dexpaprika.com/get-started/sdk-ts
JavaScript client library for accessing decentralized exchange data across multiple blockchain networks
See also: [REST intro](/api-reference/introduction),
[Networks](/api-reference/networks/get-a-list-of-available-blockchain-networks),
[Pools](/api-reference/pools/get-a-pool-on-a-network)
## Prerequisites
* Node.js 14.0.0 or higher
* Connection to the internet to access the DexPaprika API
* No API key needed to start
## Installation
Install the DexPaprika SDK using your preferred package manager:
```bash theme={null}
# using npm
npm install @dexpaprika/sdk
# using yarn
yarn add @dexpaprika/sdk
# using pnpm
pnpm add @dexpaprika/sdk
```
## Quick Example
```javascript theme={null}
import { DexPaprikaClient } from '@dexpaprika/sdk';
// Initialize the client
const client = new DexPaprikaClient();
// Get the price of Wrapped Ether (WETH) on Ethereum
async function getWethPrice() {
const wethAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
const token = await client.tokens.getDetails('ethereum', wethAddress);
console.log(`Current WETH price: $${token.price_usd}`);
}
getWethPrice();
```
## API Methods Reference
All API methods in the DexPaprika SDK follow a consistent pattern, accepting required parameters first, followed by an optional `options` object for additional configuration. This options pattern provides flexibility while keeping the API clean and easy to use.
For example, most listing methods accept pagination and sorting options:
```javascript theme={null}
// Get pools with custom pagination and sorting
const pools = await client.pools.listByNetwork('ethereum', {
page: 0, // start at first page
limit: 20, // get 20 results
sort: 'desc', // sort descending
orderBy: 'volume_usd' // sort by volume
});
```
### client.networks.list()
**Endpoint:** [GET `/networks`](/api-reference/networks/get-a-list-of-available-blockchain-networks)
Retrieves all supported blockchain networks and their metadata.
**Parameters:** None
**Returns:** Array of network objects containing network ID, name, and other information.
```javascript theme={null}
const networks = await client.networks.list();
console.log(`Supported networks: ${networks.length}`);
```
### client.dexes.listByNetwork(networkId, options)
**Endpoint:** [GET `/networks/{network}/dexes`](/api-reference/dexes/get-a-list-of-available-dexes-on-a-network)
Retrieves all DEXes on a specific network.
**Parameters:**
* `networkId`\* - Network ID (e.g., "ethereum", "solana")
* `options` - Optional configuration:
* `page` - Page number for pagination (defaults to 0)
* `limit` - Number of DEXes per page (defaults to 10)
**Returns:** Paginated list of DEX objects with name, ID, and metadata.
```javascript theme={null}
const dexes = await client.dexes.listByNetwork('ethereum', { limit: 20 });
dexes.dexes.forEach(dex => console.log(dex.dex_name));
```
### client.pools.list(options)
**Removed.** The `GET /pools` endpoint returns `410 Gone`, and this method throws
`DeprecatedEndpointError` rather than making a request. Use
`client.pools.listByNetwork(network, options)` and pass a network.
***
### client.pools.listByNetwork(networkId, options)
**Endpoint:** [GET `/networks/{network}/pools/search`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets pools on a specific network with cursor pagination and sorting options.
**Parameters:**
* `networkId`\* - ID of the network
* `options` - Options object for pagination and sorting:
* `limit` - Number of pools per page
* `sort` - Sort direction ('asc' or 'desc')
* `orderBy` - Field to sort by ('volume\_usd', 'price\_usd', etc.). Legacy values are mapped to the canonical sort fields.
* `cursor` - Cursor for the next page, taken from `next_cursor` on the previous response
**Returns:** `results` array plus `has_next_page` and `next_cursor` for cursor pagination.
```javascript theme={null}
const ethereumPools = await client.pools.listByNetwork('ethereum', {
limit: 5,
orderBy: 'volume_usd_24h',
sort: 'desc'
});
console.log(`Top pool volume: $${ethereumPools.results[0].volume_usd_24h}`);
if (ethereumPools.has_next_page) {
const nextPage = await client.pools.listByNetwork('ethereum', {
limit: 5,
cursor: ethereumPools.next_cursor
});
}
```
***
### client.pools.listByDex(networkId, dexId, options)
**`GET /networks/{network}/dexes/{dex}/pools` was removed and returns `410 Gone`.**
This method now calls `GET /networks/{network}/pools/search?dex_name=...` instead. The DEX id
moved out of the path and into a filter, so `page` is gone and the response shape changed.
See [pool filtering](/tutorials/pool-filtering).
**Endpoint:** [GET `/networks/{network}/pools/search?dex_name=...`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets pools on a specific DEX within a network.
**Parameters:**
* `networkId`\* - ID of the network
* `dexId`\* - ID of the DEX, the `dex_id` field from `client.dexes.listByNetwork()`, case-insensitive (a display name like `Uniswap V3` returns no rows instead of an error)
* `options` - Options object for pagination and sorting:
* `limit` - Number of pools per page (max 100)
* `sort` - Sort direction ('asc' or 'desc')
* `orderBy` - Field to sort by ('volume\_usd\_24h', 'liquidity\_usd', 'txns\_24h', and the rest of the canonical list)
* `cursor` - Cursor for the next page, taken from `next_cursor` on the previous response
There is no `page` option any more. The SDK normalizes legacy sort values, so `volume_usd` is
sent as `order_by=volume_usd_24h`; REST rejects the legacy spelling with a `400` that lists the
values it will take.
**Returns:** A `results` array plus `has_next_page` and `next_cursor` for cursor pagination.
```javascript theme={null}
const uniswapPools = await client.pools.listByDex('ethereum', 'uniswap_v3', {
limit: 10,
orderBy: 'volume_usd_24h',
sort: 'desc'
});
for (const pool of uniswapPools.results) {
console.log(`${pool.dex_name}: $${pool.volume_usd_24h.toLocaleString()} 24h volume`);
}
```
***
### client.pools.getDetails(networkId, poolAddress, options)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}`](/api-reference/pools/get-a-pool-on-a-network)
Gets detailed information about a specific pool.
**Parameters:**
* `networkId`\* - ID of the network
* `poolAddress`\* - On-chain address of the pool
* `options` - Options object:
* `inversed` - Whether to invert the price ratio (boolean)
**Returns:** Detailed pool information including tokens, volumes, liquidity, and more.
```javascript theme={null}
// Get details for a specific pool (WETH/USDC on Uniswap V2)
const pool = await client.pools.getDetails(
'ethereum',
'0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc',
{ inversed: false }
);
console.log(`Pool: ${pool.token0.symbol}/${pool.token1.symbol}`);
```
***
### client.pools.getTransactions(networkId, poolAddress, options)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}/transactions`](/api-reference/pools/get-transactions-of-a-pool-on-a-network-paging-can-be-used-up-to-100-pages)
Gets transaction history for a specific pool with pagination.
**Parameters:**
* `networkId`\* - ID of the network
* `poolAddress`\* - On-chain address of the pool
* `options` - Options object for pagination:
* `page` - Page number for pagination
* `limit` - Number of transactions per page
* `cursor` - Transaction ID for cursor-based pagination
**Returns:** List of transactions with details about tokens, amounts, and timestamps.
```javascript theme={null}
// Get the latest 20 transactions for a pool
const transactions = await client.pools.getTransactions(
'ethereum',
'0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc',
{ limit: 20 }
);
console.log(`Latest transaction: ${transactions.data[0].hash}`);
```
***
### client.pools.getOHLCV(networkId, poolAddress, options)
**Endpoint:** [GET `/networks/{network}/pools/{pool_address}/ohlcv`](/api-reference/pools/get-ohlcv-data-for-a-pool-pair)
Gets OHLCV (Open, High, Low, Close, Volume) chart data for a pool.
**Parameters:**
* `networkId`\* - ID of the network
* `poolAddress`\* - On-chain address of the pool
* `options`\* - OHLCV options object:
* `start`\* - Start time (ISO date string or timestamp)
* `end` - End time (optional)
* `limit` - Number of data points to return
* `interval` - Time interval ('1h', '6h', '24h', etc.)
* `inversed` - Whether to invert the price ratio (boolean)
**Returns:** Array of OHLCV data points for the specified time range and interval.
```javascript theme={null}
// Get hourly price data for the past week
const startDate = new Date();
startDate.setDate(startDate.getDate() - 7);
const ohlcvData = await client.pools.getOHLCV(
'ethereum',
'0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc',
{
start: startDate.toISOString(),
interval: '1h',
limit: 168 // 7 days * 24 hours
}
);
console.log(`Data points: ${ohlcvData.length}`);
console.log(`Current price: ${ohlcvData[ohlcvData.length-1].close}`);
```
### client.tokens.getDetails(networkId, tokenAddress)
**Endpoint:** [GET `/networks/{network}/tokens/{token_address}`](/api-reference/tokens/get-a-tokens-latest-data-on-a-network)
Gets detailed information about a specific token on a network.
**Parameters:**
* `networkId`\* - ID of the network
* `tokenAddress`\* - Token address or identifier
**Returns:** Detailed token information including price, volume, and metadata.
```javascript theme={null}
// Get details for WETH on Ethereum
const weth = await client.tokens.getDetails(
'ethereum',
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
);
console.log(`${weth.name} (${weth.symbol}): $${weth.price_usd}`);
```
***
### client.tokens.getPools(networkId, tokenAddress, options)
**Endpoint:** [GET `/networks/{network}/pools/search`](/api-reference/pools/advanced-pool-filtering-on-a-specific-network)
Gets a list of liquidity pools that include the specified token. Backed by the unified
pool search endpoint with its `token_address` filter; the old
`/networks/{network}/tokens/{token_address}/pools` endpoint was removed and returns `410 Gone`.
The filter is network-scoped, so a network is always required. An unknown token address
returns an empty result set rather than an error.
**Parameters:**
* `networkId`\* - ID of the network
* `tokenAddress`\* - Token address or identifier
* `options` - Options object for sorting and pagination:
* `limit` - Number of pools per page
* `sort` - Sort direction ('asc' or 'desc')
* `orderBy` - Field to sort by ('volume\_usd\_24h', 'liquidity\_usd', 'txns\_24h', and the rest of the canonical list). Legacy values are mapped, so `volume_usd` is sent as `order_by=volume_usd_24h`; REST rejects the legacy spelling with `400`.
* `cursor` - Cursor for the next page, taken from `next_cursor` on the previous response
`pairWith` is no longer supported. Pool search has no pair filter, so the option is
accepted for backwards compatibility but never sent. To find a specific pair, filter
the returned pools on the second token yourself.
**Returns:** `results` array plus `has_next_page` and `next_cursor` for cursor pagination.
```javascript theme={null}
// Find the busiest WETH pools on Ethereum
const wethAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
const pools = await client.tokens.getPools(
'ethereum',
wethAddress,
{
limit: 5,
orderBy: 'volume_usd_24h',
sort: 'desc'
}
);
pools.results.forEach(pool => {
console.log(`${pool.dex_name}: $${pool.volume_usd_24h} 24h volume`);
});
```
### client.search.search(query)
**Endpoint:** [GET `/search`](/api-reference/search/search-for-tokens-pools-and-dexes)
Searches for tokens, pools, and DEXes by name or identifier.
**Parameters:**
* `query`\* - Search term (e.g., "uniswap", "bitcoin", or a token address)
**Returns:** Search results organized by category (tokens, pools, DEXes).
```javascript theme={null}
// Search for "ethereum"
const results = await client.search.search('ethereum');
console.log(`Found ${results.tokens.length} tokens`);
console.log(`Found ${results.pools.length} pools`);
console.log(`Found ${results.dexes.length} DEXes`);
```
### client.utils.getStats()
**Endpoint:** [GET `/stats`](/api-reference/utils/retrieve-high-level-asset-statistics)
Gets high-level statistics about the DexPaprika ecosystem.
**Parameters:** None
**Returns:** Statistics about chains, DEXes, pools, and tokens.
```javascript theme={null}
const stats = await client.utils.getStats();
console.log(`Networks: ${stats.networks}`);
console.log(`DEXes: ${stats.dexes}`);
console.log(`Pools: ${stats.pools}`);
console.log(`Tokens: ${stats.tokens}`);
```
## Complete Example
```javascript theme={null}
import { DexPaprikaClient } from '@dexpaprika/sdk';
async function main() {
// Initialize client
const client = new DexPaprikaClient();
// Get Ethereum network details
const networks = await client.networks.list();
const ethereum = networks.find(n => n.id === 'ethereum');
console.log(`Found ${ethereum.name} with ${ethereum.dexes_count} DEXes`);
// Get WETH token details
const weth = await client.tokens.getDetails(
'ethereum',
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
);
console.log(`${weth.name} price: $${weth.price_usd}`);
// Find WETH/USDC pools
const pools = await client.tokens.getPools(
'ethereum',
'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH
{
limit: 5,
sort: 'desc',
orderBy: 'volume_usd',
pairWith: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' // USDC
}
);
// Show top pools
console.log('Top WETH/USDC pools:');
pools.data.forEach(pool => {
console.log(`${pool.dex.name}: $${pool.volume_usd_24h.toLocaleString()} 24h volume`);
});
}
main().catch(console.error);
```
## Advanced Features
### Error Handling
```javascript theme={null}
import { DexPaprikaClient, parseError } from '@dexpaprika/sdk';
// Basic error handling
try {
const client = new DexPaprikaClient();
const token = await client.tokens.getDetails('ethereum', '0xinvalidaddress');
} catch (error) {
// Using the helper to extract the most relevant error message
console.error('Error:', parseError(error));
// Or handle specific error cases manually
if (error.response?.status === 404) {
console.error('Resource not found');
} else if (error.response?.status === 429) {
console.error('Rate limit exceeded');
}
}
// The SDK automatically retries on these status codes:
// 408 (Request Timeout), 429 (Too Many Requests),
// 500, 502, 503, 504 (Server Errors)
// Custom retry configuration
const client = new DexPaprikaClient('https://api.dexpaprika.com', {}, {
retry: {
maxRetries: 3,
delaySequenceMs: [200, 500, 1000],
retryableStatuses: [429, 500, 503]
}
});
```
### Caching
```javascript theme={null}
import { DexPaprikaClient, Cache } from '@dexpaprika/sdk';
// The SDK includes built-in caching by default
// This example shows how to configure it
// Configure caching with custom settings
const client = new DexPaprikaClient('https://api.dexpaprika.com', {}, {
cache: {
ttl: 60 * 1000, // 1 minute cache TTL (default is 5 minutes)
maxSize: 100, // Store up to 100 responses (default is 1000)
enabled: true // Enable caching (enabled by default)
}
});
// Demonstration of cache behavior
async function demonstrateCaching() {
console.time('First call');
await client.networks.list(); // Makes an API request
console.timeEnd('First call');
console.time('Second call');
await client.networks.list(); // Returns cached result
console.timeEnd('Second call');
// You can also manage the cache manually
client.clearCache(); // Clear all cached data
console.log(client.cacheSize); // Get current cache size
client.setCacheEnabled(false); // Disable caching
}
// Using the Cache class directly
const manualCache = new Cache({
ttl: 30 * 1000, // 30 second TTL
maxSize: 50 // Store maximum 50 items
});
manualCache.set('myKey', { data: 'example data' });
const data = manualCache.get('myKey');
manualCache.delete('myKey');
manualCache.clear();
```
### Pagination Helper
```javascript theme={null}
import { DexPaprikaClient } from '@dexpaprika/sdk';
async function fetchAllPools(networkId) {
const client = new DexPaprikaClient();
const allPools = [];
const limit = 50;
let cursor;
while (true) {
const response = await client.pools.listByNetwork(
networkId,
{
limit,
sort: 'desc',
orderBy: 'volume_usd_24h',
cursor
}
);
allPools.push(...response.results);
// The search endpoint is cursor-paginated, not page-numbered
if (!response.has_next_page || !response.next_cursor) break;
cursor = response.next_cursor;
// Free allows 30 requests a minute, so pause 2 seconds between pages
await new Promise(resolve => setTimeout(resolve, 2000));
}
console.log(`Fetched a total of ${allPools.length} pools on ${networkId}`);
return allPools;
}
// Usage example
fetchAllPools('ethereum').catch(console.error);
```
### Custom Configuration
```javascript theme={null}
import { DexPaprikaClient } from '@dexpaprika/sdk';
import axios from 'axios';
// Create a custom axios instance
const axiosInstance = axios.create({
timeout: 60000, // 60 second timeout
headers: {
'User-Agent': 'MyApp/1.0 DexPaprikaSDK'
}
});
// Create client with custom configuration
const client = new DexPaprikaClient(
'https://api.dexpaprika.com', // Base URL (optional)
axiosInstance, // Custom axios instance (optional)
{
// Retry configuration (optional)
retry: {
maxRetries: 5,
delaySequenceMs: [100, 500, 1000, 2000, 5000],
retryableStatuses: [429, 500, 502, 503, 504]
},
// Cache configuration (optional)
cache: {
ttl: 10 * 60 * 1000, // 10 minutes TTL
maxSize: 500, // Store up to 500 responses
enabled: true // Enable caching
}
}
);
// Usage example
async function fetchWithCustomClient() {
try {
const networks = await client.networks.list();
console.log(`Fetched ${networks.length} networks with custom client`);
} catch (err) {
console.error('Error:', err);
}
}
```
## Resources
* [GitHub Repository](https://github.com/coinpaprika/dexpaprika-sdk-ts)
* [DexPaprika Website](https://dexpaprika.com)
* [API Reference](/api-reference/introduction)
## API Status
The DexPaprika API provides consistent data with stable endpoints. No API key is currently required to access the service. We aim to maintain backward compatibility and provide notice of any significant changes.
### FAQs
Not to start. Keyless requests work at 50,000 credits a month per IP, and a [free registered key](https://console.dexpaprika.com/dashboard) raises that to 300,000 with no card.
Use Coverage Checker or list Networks and query Tokens/Pools to discover addresses.
Use pools/transactions endpoints with `pool_address`, `network`, and time/paging params as documented.
30 requests a minute on the free tier, 300 on Pro, against a monthly allowance of 50,000 credits keyless, 300,000 with a free key, or 5,000,000 on Pro. Retry transient HTTP errors with backoff. See [rate limits](/knowledge-base/rate-limits) for how the counters work, and [Pro pricing](https://dexpaprika.com/api/pricing) for what the 5,000,000 credit tier costs.
# DexPaprika crypto DEX API - on-chain liquidity & swap data for developers
Source: https://docs.dexpaprika.com/introduction
Real‑time DEX and on‑chain cryptocurrency data: liquidity pools, swaps, token prices, and transactions across 36 blockchain networks via REST API and SSE streaming.
**Need more than the free allowance?** The [Pro API](/api-pro/introduction) includes 5,000,000 credits a month at 300 requests a minute, real-time data instead of the free tier's delay of up to 15 seconds, dedicated infrastructure, and priority support. Pro is \$99 a month, with plans and checkout on the [pricing page](https://dexpaprika.com/api/pricing). Enterprise is the same API with limits raised for your workload.
See also: [Networks](/api-reference/networks/get-a-list-of-available-blockchain-networks),
[Liquidity pool endpoint](/api-reference/pools/get-a-pool-on-a-network),
[Swap transactions](/api-reference/pools/get-transactions-of-a-pool-on-a-network-paging-can-be-used-up-to-100-pages),
[Token data](/api-reference/tokens/get-a-tokens-latest-data-on-a-network),
[Real-time streaming](/streaming/introduction)
Jump straight into the API documentation and start making requests within minutes
Learn step-by-step how to integrate DexPaprika into your applications
Subscribe to live token prices and pool reserve updates over SSE. No API key needed to start, no polling.
## DEX API quickstart
You can also make your first request directly in our API Playground available in the API Reference section. Visit [GET Token](/api-reference/tokens/get-a-tokens-latest-data-on-a-network) to try it out.
In the next steps we will make a GET request to the [Token](/api-reference/tokens/get-a-tokens-latest-data-on-a-network) endpoint in order to get the latest price in USD of SOL. All we need is the network ID and the token address.
* Network ID: `solana`
* Token address: `So11111111111111111111111111111111111111112`
You can find the list of all supported networks in the [Networks](/api-reference/networks/get-a-list-of-available-blockchain-networks) endpoint.
Simply make a GET request to the following endpoint in order to fetch latest data about any token. In this case we will use the specified above network ID and token address to populate the endpoint in a format of `https://api.dexpaprika.com/networks/{network_id}/tokens/{address}`.
```bash bash theme={null}
curl -X GET "https://api.dexpaprika.com/networks/solana/tokens/So11111111111111111111111111111111111111112"
```
```python python.py theme={null}
import requests
response = requests.get("https://api.dexpaprika.com/networks/solana/tokens/So11111111111111111111111111111111111111112")
print(response.json())
```
```javascript javascript.js theme={null}
fetch("https://api.dexpaprika.com/networks/solana/tokens/So11111111111111111111111111111111111111112")
.then(response => response.json())
.then(data => console.log(data));
```
This will return quite lengthy response of the latest data about Solana (SOL) that will look similar to this. We send a lot of data in the response, so in the next step we will extract only the price of SOL in USD.
```json Response [expandable] theme={null}
{
"id": "So11111111111111111111111111111111111111112",
"name": "Wrapped SOL",
"symbol": "SOL",
"chain": "solana",
"decimals": 9,
"total_supply": 8765530.584758602,
"description": "",
"website": "",
"has_image": false,
"added_at": "2024-10-04T08:30:05Z",
"price_stats": {
"high_24h": 90.42,
"low_24h": 79.83,
"ath": 2094.38,
"ath_date": "2025-02-18T22:43:00Z"
},
"summary": {
"chain": "solana",
"id": "So11111111111111111111111111111111111111112",
"price_usd": 84.70,
"fdv": 742439936.27,
"liquidity_usd": 720592129.48,
"pools": 68563,
"24h": {
"volume": 107273758.93,
"volume_usd": 9250815075.33,
"sells": 15608060,
"buys": 11886100,
"txns": 27495179,
"buy_usd": 4623901804.94,
"sell_usd": 4626913270.38,
"last_price_usd_change": 1.37
},
"6h": {
"volume": 26773795.06,
"volume_usd": 2287803995.05,
"sells": 3934865,
"buys": 2727726,
"txns": 6662749,
"buy_usd": 1137172848.86,
"sell_usd": 1150631146.19,
"last_price_usd_change": -2.20
},
"1h": {
"volume": 3910905.40,
"volume_usd": 329548955.23,
"sells": 637805,
"buys": 431908,
"txns": 1069763,
"buy_usd": 163705382.93,
"sell_usd": 165843572.30,
"last_price_usd_change": -0.74
},
"30m": {
"volume": 2433489.04,
"volume_usd": 204746406.88,
"sells": 318226,
"buys": 221887,
"txns": 540140,
"buy_usd": 101344488.38,
"sell_usd": 103401918.50,
"last_price_usd_change": -0.34
},
"15m": {
"volume": 850690.44,
"volume_usd": 71384438.53,
"sells": 163924,
"buys": 110052,
"txns": 273982,
"buy_usd": 35058520.01,
"sell_usd": 36325918.52,
"last_price_usd_change": -0.27
},
"5m": {
"volume": 240013.44,
"volume_usd": 20122986.10,
"sells": 48267,
"buys": 33557,
"txns": 81826,
"buy_usd": 9957259.82,
"sell_usd": 10165726.28,
"last_price_usd_change": 0.05
}
},
"last_updated": "2026-03-03T10:19:44.540471814Z"
}
```
As you can see, the response is very rich of data. You can extract only the price of SOL in USD from the full response by navigating to the `summary` object (*or any other object*) and then to the `price_usd` key (*or any other key*) simply by modyfying the previously used GET request:
```bash bash theme={null}
curl -s "https://api.dexpaprika.com/networks/solana/tokens/So11111111111111111111111111111111111111112" | jq '.summary.price_usd'
```
```python python.py theme={null}
import requests
response = requests.get("https://api.dexpaprika.com/networks/solana/tokens/So11111111111111111111111111111111111111112")
print(response.json()["summary"]["price_usd"])
```
```javascript javascript.js theme={null}
fetch("https://api.dexpaprika.com/networks/solana/tokens/So11111111111111111111111111111111111111112")
.then(response => response.json())
.then(data => {
const onlyPrice = data.summary.price_usd;
console.log(onlyPrice);
});
```
And as a result you will get the price of SOL in USD:
```json summary.price_usd theme={null}
84.70
```
Congratulations! You just successfully retrieved the latest price of SOL in USD!
Pool search:filter pools by volume, liquidity, transactions, and creation date on /networks/\{network}/pools/search. Plus batch token prices in a single request.
## Need real-time data?
The REST API is the right choice when you're querying historical or on-demand data. For applications that need live updates the moment a swap or liquidity change settles on-chain, DexPaprika ships two SSE streaming feeds. No polling, no log decoding, no API key needed to start.
Subscribe to USD price updates for any token across 36+ chains. An event is pushed when a swap moves the price, so the rate tracks how actively the asset trades.
Pool reserve deltas, USD-denominated, pushed for each block in which a subscribed pool's reserves changed. For any DEX pool or any token.
## Next steps
Jump straight into the API documentation and start making requests within minutes
Learn step-by-step how to integrate DexPaprika into your applications
## Popular use cases
Get real-time and historical price data for any token across 36 blockchains
Access detailed liquidity pool data across major DEXes on any network
Analyze trading volumes, price changes, and market trends
Compare prices and liquidity across different DEXes
## DexPaprika API support
We're here to help you succeed with DexPaprika.
Connect with our community and get real-time support
Share your experience and help us improve
## Explore supported on-chain networks
Browse the full [networks list](/api-reference/networks/get-a-list-of-available-blockchain-networks) to find the correct `network` parameter for your requests.
### FAQs
Not to start. The API answers keyless requests at 50,000 credits a month per IP. [Registering a free key](https://console.dexpaprika.com) raises that to 300,000 and needs no card. See [rate limits](/knowledge-base/rate-limits).
Liquidity pools, swap transactions, token metrics/prices, and network coverage across multiple chains.
Use the Coverage Checker tool or the Networks/Token endpoints to locate addresses.
Collection is near real-time and REST responses reflect the latest indexed blocks and events. Your plan sets the ceiling: the free tier is served with a delay of up to 15 seconds, Pro is real time. For push updates, use the [streaming API](/streaming/introduction) (SSE).
Yes. Three SSE feeds run on `streaming.dexpaprika.com`: `/sse/prices` pushes token-price updates, `/sse/reserves` pushes pool reserve deltas per block in which reserves changed, and `/sse/transactions` pushes individual swaps with USD amounts. No API key is needed to start. See the [Streaming introduction](/streaming/introduction) or the [reserves deep-dive](/streaming/reserves-streaming).
**Looking for enterprise solutions?** We offer dedicated support, higher rate limits, and custom features.
[Contact our team](mailto:support@coinpaprika.com) to learn more.
# Common patterns and workflows
Source: https://docs.dexpaprika.com/knowledge-base/common-patterns
Standard workflows for DexPaprika API: looking up token prices, finding pools, getting historical data, filtering pools, batch pricing, and combining REST with streaming for real-time updates.
## Overview
This page covers the most common sequences of API calls for typical tasks. Each pattern shows the exact endpoints, parameters, and response fields you need.
Base URL: `https://api.dexpaprika.com`
***
## Pattern 1: Get a token's price
**When you know the network and token address:**
```bash theme={null}
curl "https://api.dexpaprika.com/networks/solana/tokens/So11111111111111111111111111111111111111112"
```
The price is at `response.summary.price_usd`.
**When you only know the token name or symbol:**
1. Search first:
```bash theme={null}
curl "https://api.dexpaprika.com/search?query=jupiter"
```
2. From the `tokens` array in the response, find the matching token. Note the `chain` and `id` (address) fields.
3. Call the token endpoint:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/{chain}/tokens/{id}"
```
***
## Pattern 2: Compare prices of multiple tokens
Use batch pricing when you need prices for 2 to 10 tokens on the same network:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/multi/prices?tokens=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2,0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
```
Response is an array of `{id, chain, price_usd}` objects. Order is not guaranteed. Tokens without pricing data are silently omitted (not an error).
**Limits:** Max 10 tokens per request. More than 10 returns HTTP 400. Zero tokens also returns HTTP 400.
For tokens across different networks, make separate requests per network.
***
## Pattern 3: Find top pools on a network
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?limit=10&order_by=volume_usd_24h&sort=desc"
```
**Sorting options for `order_by`:** `volume_usd_24h`, `volume_usd_7d`, `volume_usd_30d`, `liquidity_usd`, `txns_24h`, `created_at`, `price_usd`, `price_change_percentage_24h`, `price_change_percentage_6h`, `price_change_percentage_1h`, `price_change_percentage_5m`. Anything else returns HTTP 400 with the valid list in the message.
**Pagination:** Cursor-based. Read `has_next_page` and `next_cursor`, then pass `next_cursor` back as `cursor`.
The response wraps rows in a `results` array (not `pools`).
***
## Pattern 4: Find pools for a specific token
```bash theme={null}
curl "https://api.dexpaprika.com/networks/solana/pools/search?token_address=So11111111111111111111111111111111111111112&order_by=volume_usd_24h&sort=desc&limit=5"
```
This returns pools on that network containing the token, sorted by volume. The highest-volume pool is typically the best source for price data and OHLCV history.
**Network-scoped only:** The `token_address` filter works on `GET /networks/{network}/pools/search`. The cross-network `GET /pools/search` accepts the parameter but silently ignores it. One token per query; repeating `token_address` does not act as a pair filter, and the API uses only one of the values (not guaranteed by order). An unknown address returns HTTP 200 with an empty `results` array.
The old `GET /networks/{network}/tokens/{token_address}/pools` endpoint was removed and returns `410 Gone`. Its `reorder` and second-token `address` parameters have no equivalent on pool search.
***
## Pattern 5: Get historical price data (OHLCV)
OHLCV data is per pool, not per token. The workflow is:
1. **Find the best pool.** Take the highest-volume pool for the token:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?token_address=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&order_by=volume_usd_24h&sort=desc&limit=1"
```
2. **Get OHLCV data** for that pool:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/{pool_address}/ohlcv?start=2025-06-01&interval=24h&limit=30"
```
**Intervals:** `1m`, `5m`, `10m`, `15m`, `30m`, `1h`, `6h`, `12h`, `24h`
**`start` is required.** Give it an ISO 8601 date or a UNIX timestamp. Optional `end` parameter (max 1 year from start). Max 366 data points per request.
Response is a JSON array of candlestick objects with `time_open`, `time_close`, `open`, `high`, `low`, `close`, `volume`.
Use `inversed=true` to flip the pair (e.g., get ETH/USDC instead of USDC/ETH).
***
## Pattern 6: Filter pools by criteria
Use the pool search endpoint to find pools matching specific conditions:
```bash theme={null}
# High-volume Ethereum pools
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?volume_usd_24h_min=100000&order_by=volume_usd_24h&sort=desc"
# New Solana pools with activity (created after a specific date)
curl "https://api.dexpaprika.com/networks/solana/pools/search?created_after=1709251200&txns_24h_min=50&order_by=created_at&sort=desc"
```
**Available filter parameters:**
| Parameter | Type | Description |
| ------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `volume_usd_24h_min` | number | Minimum 24h volume in USD |
| `volume_usd_24h_max` | number | Maximum 24h volume in USD |
| `volume_usd_7d_min` / `_max` | number | 7-day volume in USD |
| `volume_usd_30d_min` / `_max` | number | 30-day volume in USD |
| `liquidity_usd_min` / `_max` | number | Pool liquidity in USD |
| `txns_24h_min` | integer | Minimum transactions in last 24h |
| `price_change_percentage_24h_min` / `_max` | number | 24h price change, percent |
| `price_change_percentage_6h_min` / `_max` | number | 6h price change, percent |
| `price_change_percentage_1h_min` / `_max` | number | 1h price change, percent |
| `price_change_percentage_5m_min` / `_max` | number | 5m price change, percent |
| `created_after` | integer | UNIX timestamp, only pools created after this |
| `created_before` | integer | UNIX timestamp, only pools created before this |
| `order_by` | string | `volume_usd_24h`, `volume_usd_7d`, `volume_usd_30d`, `liquidity_usd`, `txns_24h`, `created_at`, `price_usd`, `price_change_percentage_24h`, `price_change_percentage_6h`, `price_change_percentage_1h`, `price_change_percentage_5m` |
| `sort` | string | `asc` or `desc` |
| `limit` | integer | Items per page (default: 10) |
| `cursor` | string | Pass `next_cursor` from the previous response to page forward |
All filters combine with AND logic. The response wraps rows in a `results` array (not `pools`) and pages with `has_next_page` + `next_cursor` (cursor-based; there is no `page_info`/`total_pages`).
Use the canonical `volume_usd_*` parameter names (e.g. `volume_usd_7d_min`, `volume_usd_30d_max`). The volume, liquidity, and transaction filters are all functional. Older short names like `volume_7d_min` are silently ignored.
Percentage filters take signed numbers, so `price_change_percentage_1h_max=-20` screens for pools down 20 percent or more over the last hour. Only the 6h, 1h and 5m windows are pool-only. Token search takes the 24h window for both sorting and filtering, rejects the three short windows as `order_by` with a 400, and silently drops them as `_min` / `_max` bounds.
***
## Pattern 7: Monitor pool transactions
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/transactions?limit=20&page=1"
```
Transactions are returned in reverse chronological order. Each includes:
* `amount_0`, `amount_1`: token amounts
* `volume_0`, `volume_1`: volumes
* `price_0_usd`, `price_1_usd`: USD prices for each token
* `token_0_symbol`, `token_1_symbol`: token symbols
* `type`: `swap`, `add`, or `remove`
* `created_at`: timestamp
**Pagination:** Max 100 pages. For deep history, use `cursor` parameter (a transaction ID) instead of page numbers.
***
## Pattern 8: Discover DEXes on a network
```bash theme={null}
# List all DEXes on a network
curl "https://api.dexpaprika.com/networks/solana/dexes"
# Get pools for a specific DEX
curl "https://api.dexpaprika.com/networks/solana/pools/search?dex_name=raydium&order_by=volume_usd_24h&sort=desc&limit=10"
```
The old `GET /networks/{network}/dexes/{dex}/pools` endpoint was removed and returns `410 Gone`. The DEX is now the `dex_name` filter on pool search, which accepts the `dex_id` (`raydium`) or the `dex_name` (`Raydium`) from the DEX list above. Rows come back under `results` with `has_next_page` + `next_cursor`, and the sort field is `volume_usd_24h`.
***
## Pattern 9: Stream live prices
For real-time updates, use the streaming API instead of polling REST.
**Single token (GET):**
```bash theme={null}
curl -N "https://streaming.dexpaprika.com/sse/prices?method=token_price&chain=ethereum&address=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
```
**Multiple tokens (POST), up to 25 per connection:**
```bash theme={null}
curl -N -X POST "https://streaming.dexpaprika.com/sse/prices" \
-H "Accept: text/event-stream" \
-H "Content-Type: application/json" \
-d '[
{"chain": "solana", "address": "So11111111111111111111111111111111111111112", "method": "token_price"},
{"chain": "ethereum", "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "method": "token_price"}
]'
```
Each SSE event:
```
data: {"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","chain":"ethereum","price":"2255.59","timestamp":1778847592,"timestamp_price":1778847592,"token_price":1778847592}
event: token_price
```
The `price` field is a string, not a number, so parse it as a decimal for precision. Open additional connections in parallel if you need more than 25 subscriptions, up to **10 concurrent SSE streams per IP**.
***
## Pattern 10: REST + Streaming combined
The most common production pattern:
1. **REST for discovery.** Use search, token details, and pool listing to find what you want to track
2. **Validate.** Confirm the tokens exist and have pricing data via REST
3. **Stream for live updates.** Open an SSE connection for real-time prices
4. **REST for enrichment.** Periodically call REST for OHLCV history, pool details, or transaction data that streaming doesn't cover
***
## Quick reference: which endpoint for what?
| I want to... | Endpoint |
| ----------------------------------------- | -------------------------------------------------------------------------------------- |
| Get a token's current price | `GET /networks/{network}/tokens/{address}` → `.summary.price_usd` |
| Get multiple token prices at once | `GET /networks/{network}/multi/prices?tokens=a,b,c` |
| Find a token I don't have the address for | `GET /search?query={name_or_symbol}` |
| See top pools by volume | `GET /networks/{network}/pools/search?order_by=volume_usd_24h&sort=desc` |
| Find pools for a specific token | `GET /networks/{network}/pools/search?token_address={address}` |
| Filter pools by volume/txns/age | `GET /networks/{network}/pools/search?volume_usd_24h_min=X` |
| Get historical candlestick data | `GET /networks/{network}/pools/{pool}/ohlcv?start=X&interval=24h` |
| See recent swaps on a pool | `GET /networks/{network}/pools/{pool}/transactions` |
| List DEXes on a network | `GET /networks/{network}/dexes` |
| Get pools on a specific DEX | `GET /networks/{network}/pools/search?dex_name={dex_id}` |
| Stream live prices | `GET https://streaming.dexpaprika.com/sse/prices?method=token_price&chain=X&address=Y` |
| Get API stats | `GET /stats` |
### FAQs
If all requested tokens are unknown or don't have pricing data, you get HTTP 200 with an empty array, not an error. Verify the token addresses are correct.
`GET /networks/{network}/pools`, `/pools`, and `/networks/{network}/pools/filter` were removed (they return `410 Gone`). Use `GET /networks/{network}/pools/search` (single network) or `GET /pools/search` (multiple networks via a `chains` filter). The search response wraps rows in a `results` array; each pool uses `id` (the pool address), `volume_usd_24h`, and `transactions_24h`. Pagination is cursor-based (`has_next_page` + `next_cursor`).
Use the highest-volume pool. It has the most representative pricing. Find it via `GET /networks/{network}/pools/search?token_address={address}&limit=1` (the default sort is `volume_usd_24h` descending).
`GET /networks/{network}/tokens/{token_address}/pools` was removed (it returns `410 Gone`). Use `GET /networks/{network}/pools/search` with the `token_address` query parameter. The filter is network-scoped only: the cross-network `GET /pools/search` accepts `token_address` but silently ignores it. The old `reorder` and second-token `address` parameters have no equivalent.
`GET /networks/{network}/dexes/{dex}/pools` was removed (it returns `410 Gone`). Use `GET /networks/{network}/pools/search` with the `dex_name` query parameter, which takes the `dex_id` field from `GET /networks/{network}/dexes` (`uniswap_v3`), matched case-insensitively. Passing that response's `dex_name` field instead, a display name like `Uniswap V3`, returns an empty `results` array rather than an error. Unlike `token_address`, `dex_name` also works on the cross-network `GET /pools/search`. Rows come back under `results` with `has_next_page` + `next_cursor`, and the 24h volume field is `volume_usd_24h`, not `volume_usd`.
# Core concepts
Source: https://docs.dexpaprika.com/knowledge-base/concepts
Understand the key concepts behind DexPaprika: blockchain networks, DEXes, liquidity pools, tokens, OHLCV data, and how on-chain data flows from blockchains to the API.
## How DexPaprika works
DexPaprika indexes on-chain activity from 36 blockchain networks in near real-time. It watches smart contract events on decentralized exchanges (DEXes), extracts swap transactions, liquidity changes, and price data, and serves it through a REST API and SSE streaming API.
```
Blockchains → Indexer → DexPaprika API → Your app / AI agent
(36 chains) (near real-time) (REST + Streaming)
```
No API key needed to start. Just make HTTP requests, and [register a free key](https://console.dexpaprika.com) when you want the higher allowance.
***
## Networks
A **network** (also called a chain or blockchain) is the underlying blockchain where DEXes operate. Each network has its own token addresses and pool addresses.
DexPaprika currently supports 36+ networks including Ethereum, Solana, Base, Arbitrum, Polygon, BSC, Optimism, Avalanche, Fantom, Sui, Aptos, TON, Tron, and more.
Every API call that involves a specific token or pool requires a `network` parameter, the lowercase identifier like `ethereum`, `solana`, `bsc`, `base`.
```bash theme={null}
# List all supported networks
curl "https://api.dexpaprika.com/networks"
```
The response is a JSON array of objects with `id` and `display_name`.
***
## DEXes (Decentralized Exchanges)
A **DEX** is a decentralized exchange protocol, a set of smart contracts that allows users to swap tokens without an intermediary. Examples: Uniswap, Raydium, PancakeSwap, Curve.
Each DEX operates on one or more networks. For instance, Uniswap V3 runs on Ethereum, Arbitrum, Base, Polygon, and others.
In DexPaprika, DEXes are identified by a `dex_id` string like `uniswap_v3`, `raydium`, `pancakeswap_v3`.
```bash theme={null}
# List DEXes on Ethereum
curl "https://api.dexpaprika.com/networks/ethereum/dexes"
```
***
## Liquidity pools
A **liquidity pool** is a smart contract that holds reserves of two (or more) tokens. Users swap one token for the other, and the pool's reserves shift accordingly. That shift is how prices are determined on a DEX.
Each pool has:
* **Pool address**: the smart contract address on-chain
* **Token pair**: the two tokens in the pool (e.g., USDC/WETH)
* **Liquidity**: the total USD value of tokens held in the pool
* **Volume**: how much has been traded through the pool in a time period
* **Transactions**: how many swap/add/remove events happened
* **Price**: the current exchange rate between the two tokens
Pools are the central data object in DexPaprika. Most queries revolve around finding pools, inspecting them, and tracking their activity.
```bash theme={null}
# Get the top pool on Ethereum by volume
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?limit=1&order_by=volume_usd_24h&sort=desc"
```
***
## Tokens
A **token** is a digital asset on a blockchain, identified by its contract address. The same token concept (like "USDC") can exist on multiple networks with different addresses.
DexPaprika tracks token data by aggregating activity across all pools that contain that token. The token endpoint returns:
* **Price in USD**: aggregated from pool data
* **Fully diluted valuation (FDV)**
* **Liquidity**: total across all pools
* **Volume and transactions**: across multiple time windows (24h, 6h, 1h, 30m, 15m, 5m, 1m)
* **Buy/sell counts and USD values**
* **Price stats**: 24h high/low and all-time high
```bash theme={null}
# Get SOL token data on Solana
curl "https://api.dexpaprika.com/networks/solana/tokens/So11111111111111111111111111111111111111112"
```
The price is at `response.summary.price_usd`.
***
## OHLCV data
**OHLCV** stands for Open, High, Low, Close, Volume. That is the standard candlestick format used in financial charts.
DexPaprika provides OHLCV data per pool (not per token). To get historical price data for a token, first find its highest-volume pool, then request OHLCV data for that pool.
Available intervals: `1m`, `5m`, `10m`, `15m`, `30m`, `1h`, `6h`, `12h`, `24h`
Limits: up to 366 data points per request, max 1 year range.
```bash theme={null}
# 30 days of daily candles for the USDC/WETH pool on Uniswap V3
curl "https://api.dexpaprika.com/networks/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/ohlcv?start=2025-01-01&interval=24h&limit=30"
```
***
## Transactions
**Transactions** are individual on-chain events on a pool: swaps, liquidity additions, and removals. Each transaction includes token amounts, USD values, prices, and timestamps.
Transactions are returned in reverse chronological order and are available for any pool.
```bash theme={null}
# Recent transactions for a pool
curl "https://api.dexpaprika.com/networks/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/transactions?limit=10"
```
***
## Search
The **search** endpoint lets you find tokens, pools, and DEXes by name, symbol, or address across all networks. Use this when you don't know the exact network or address for something.
```bash theme={null}
# Find "Jupiter" across all networks
curl "https://api.dexpaprika.com/search?query=jupiter"
```
The response includes `tokens`, `pools`, and `dexes` arrays with matching results.
***
## Pool filtering
The **pool search** endpoint lets you search for pools matching specific criteria: volume range, liquidity, transaction count, and creation date. This is useful for building pool screeners or finding recently created pools with significant activity.
```bash theme={null}
# Ethereum pools with >$100k daily volume, sorted by volume
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?volume_usd_24h_min=100000&order_by=volume_usd_24h&sort=desc"
```
***
## Streaming (real-time prices)
The **streaming API** delivers real-time price updates via Server-Sent Events (SSE). Instead of polling the REST API repeatedly, you open one connection and receive a price update whenever a swap moves the price. The rate tracks trading activity, from about 1 update a minute on a quiet asset to about 52 on a busy one.
Base URL: `https://streaming.dexpaprika.com`
```bash theme={null}
# Stream WETH price on Ethereum
curl -N "https://streaming.dexpaprika.com/sse/prices?method=token_price&chain=ethereum&address=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
```
Each `token_price` SSE event contains: `address`, `chain`, `price` (string, USD), and unix timestamps (`timestamp`, `timestamp_price`). The legacy `t_p` method emits the compact `{a, c, p, t, t_p}` shape and is **deprecated**.
***
## Key things to know
* **No authentication needed to start**: keyless requests work, and a free key raises the allowance
* **Credit allowance**: 50,000 credits a month keyless (300,000 with a free API key), at 30 requests a minute; one request costs one credit, batch endpoints one credit per item
* **Pagination**: the search endpoints are cursor-based (read `has_next_page`, pass `next_cursor` back as `cursor`). Only `GET /networks/{network}/dexes` and `GET /networks/{network}/pools/{pool_address}/transactions` still take `page`, and those are 1-indexed (`page=0` is silently treated as page 1)
* **Max page size**: 100 items per page across all endpoints
* **Addresses are case-sensitive**: use the exact on-chain format
* **Prices are numbers** in REST responses but **strings** in streaming SSE events (for decimal precision)
* **OHLCV is per pool, not per token**: find the highest-volume pool first, then request candles
### FAQs
DexPaprika covers on-chain DEX data (pools, swaps, on-chain token prices). CoinPaprika covers centralized exchange data (market cap rankings, exchange listings, global metrics). They're separate APIs from the same company.
The indexer processes new blocks as they arrive, typically within seconds of on-chain confirmation. What you see depends on your plan: the free tier is served with a delay of up to 15 seconds, [Pro](https://dexpaprika.com/api/pricing) is real time.
No. DexPaprika is read-only. It provides market data but does not execute trades.
# The console: keys, usage and billing
Source: https://docs.dexpaprika.com/knowledge-base/console
Where to get an API key, rotate it, watch what you have spent, connect an AI client and find your invoices.
Everything account-shaped lives in one place: [console.dexpaprika.com](https://console.dexpaprika.com). Your key, what you have spent this period, the errors your calls returned, the MCP setup for AI clients, and billing.
You do not need it to start. The API answers keyless requests, and a key is what raises the allowance and unlocks any-token streaming. See [pricing](https://dexpaprika.com/api/pricing) for what each tier includes.
## What each section is for
| Section | What you do there |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Dashboard** | The one-screen answer to "am I fine?". Current plan, credits spent against the cap, whether autoscaling is on, and when the next billing cycle starts |
| **Usage** | The same numbers with history: which endpoints you call, how the spend moved day to day |
| **Error Log** | What your failed calls actually returned. Start here before assuming the API is down |
| **API Keys** | Reveal, copy, revoke. One active key per account |
| **MCP & AI Setup** | Ready-made configuration for Claude, Cursor, Cline and anything else that speaks MCP |
| **Billing** | Plan and billing period, the autoscaling switch, credit packs, and invoices with an export |
| **Notifications** | Where quota and billing alerts are delivered |
## Frequently asked
**API Keys** in the sidebar. A key is generated for your account, and there is exactly one active key at a time.
The same screen shows the base URLs your key is valid for. Free keys authenticate against `https://api.dexpaprika.com` and `https://streaming.dexpaprika.com`. Pro moves you to `https://api-pro.dexpaprika.com` and `https://streaming-pro.dexpaprika.com`.
That pairing matters: a key sent to the wrong host answers `403 wrong_host` rather than falling back, and the body tells you which host to use.
Revoke the current one, then generate a new one. Both buttons are on the **API Keys** screen.
Because there is one active key per account, rotation is a break: the old key stops working the moment you revoke it. Have the new key ready to deploy before you press it, and if the key lives in a hosted environment, remember it is usually stored as a secret rather than in the code.
Put the key in the `Authorization` header, on its own:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search" \
-H "Authorization: api_YOUR_KEY_HERE"
```
There is no `Bearer` prefix. `Authorization: Bearer api_...` is rejected with `401`, which is easy to trip over because almost every other API expects the opposite.
**Billing**, at the bottom of the page, with an export control for pulling them out. Until your first payment the section says so rather than sitting empty, so an empty list is not a sign that something failed.
The same screen carries the rest of the money: your current plan with the period it runs for, the autoscaling switch, and the credit packs.
The dashboard shows the next billing cycle at a glance. If it reads no active subscription, you are on a free tier and there is nothing to invoice.
Both are ways to keep going past your monthly allowance on Pro. They differ in when you decide.
**Autoscaling** is a switch. Leave it off and the allowance is a hard stop, exactly like a free tier. Turn it on and once the plan is spent, extra credits are bought automatically at \$20 per million, up to a spending limit you set on the same screen. You decide the ceiling once and then stop thinking about it.
**Credit packs** are bought up front, and they cost less per credit than autoscaling does: 5M for $79, 15M for $199, 50M for \$499. The larger the pack the wider the gap, from roughly a fifth cheaper to about half.
They combine. A pack sized to what you expect, with autoscaling behind it for the month that surprises you, means you pay the lower rate for the bulk of it and never hit a wall.
Both are Pro features. On a free tier the controls are visible but inactive.
The dashboard card **Usage this period** is the short answer: credits used against your monthly cap, with the percentage. **Usage** breaks it down over time and by endpoint, so you can see which call is doing the spending.
One request costs one credit. Batch endpoints charge one credit per item, and each delivered streaming update costs one credit too. See [plan your credit usage](/knowledge-base/credit-usage) for how to size a workload before you build it.
On a free tier the cap is hard: the API answers `402` and the body names the next step for your tier. Nothing is charged, and the allowance refills when the period rolls over. The `resets_at` field in the body tells you when.
On Pro you can let it keep going. The dashboard calls this **autoscaling**: with it off the cap behaves like the free tier, with it on the overage is charged per additional block up to the monthly spend limit you set. See [error handling](/knowledge-base/error-handling) for the difference between a `402` and a `429`, which is worth getting right in code: a `429` is a per-minute rate limit and is worth retrying, a `402` is the monthly allowance and retrying will not help.
**MCP & AI Setup** generates the configuration with your key already filled in, for Claude Code, Claude Desktop and any client that takes a JSON config.
The hosted server speaks two transports. Streamable HTTP, which is what current clients use:
```
https://mcp.dexpaprika.com/streamable-http
```
and SSE, for older clients:
```
https://mcp.dexpaprika.com/sse
```
Authentication is the same `Authorization` header as REST. Full setup per client is in [the hosted MCP guide](/ai-integration/hosted-mcp-server).
No. Keyless requests work at 50,000 credits a month per IP, with no signup and no card. Registering a free key raises that to 300,000 and lifts the streaming restriction from the [showcase list](/streaming/showcase-streams) to any token we index.
# Plan your credit usage
Source: https://docs.dexpaprika.com/knowledge-base/credit-usage
Work out what your workload actually costs before you commit to a plan. Real measured update rates, four worked cost models, and the point where streaming stops costing less than polling.
An HTTP call is a request; what it spends is a credit. One request costs one credit, batch endpoints cost one credit per item, and every update delivered over a stream costs one credit. This page turns that rule into numbers you can plan against, so you can answer "will 5M credits a month cover me?" before you pay for anything.
If you only read one section, read [stream or poll](#stream-or-poll). Streaming does not automatically cost less, and on a fast-moving asset it is dramatically more expensive.
## The allowances
| Tier | Monthly credits | Requests per minute |
| -------------------- | ----------------------------------------------- | ------------------- |
| Free, no key | 50,000 per IP | 30 |
| Free, registered key | 300,000 | 30 |
| Pro | 5,000,000 included, then \$20 per additional 1M | 300 |
| Enterprise | Agreed per contract | Agreed per contract |
Full detail on the [rate limits page](/knowledge-base/rate-limits). Plans are on [pricing](https://dexpaprika.com/api/pricing).
## What a stream actually costs
Streams are pushed only when a value changes, so cost tracks the asset's update rate, not the number of connections you hold open. That rate varies enormously.
### What sets the update rate
Prices are derived from swaps, so **an update happens when a swap moves the price**. Two things bound that:
* **Swap activity.** The dominant factor, and in practice the binding one. A heavily traded asset produces a steady stream of price-moving swaps; a thinly traded one produces almost none.
* **The recompute cadence.** Our pricing pipeline recomputes on a sub-second cycle, which sets an upper bound. No asset streams faster than that, and almost none get close.
Fast blocks alone do not mean a fast stream. A chain can produce blocks quickly and still yield few updates, because with little trading there are no swaps to move the price.
The gap is larger than most people expect. Over 24 hours, `/networks` reports roughly **24.8M transactions on Solana against 6,773 on Cronos**, and the streams reflect that almost exactly: about 52 updates a minute versus about 1.3. So the useful mental model for capacity planning is that **you pay for how actively the asset trades**, not for how long you stay connected.
These are real measurements from a single 90 second sample of `/sse/prices`, taken 2026-07-31. Treat them as the right order of magnitude rather than a guarantee, since trading activity changes hour to hour:
| Asset | Updates per minute | Credits per 30 days | Share of Pro (5M credits) |
| --------------- | ------------------ | ------------------- | ------------------------- |
| SOL (solana) | 52.1 | 2,251,973 | 45% |
| WETH (base) | 30.2 | 1,306,700 | 26% |
| WPOL (polygon) | 21.9 | 945,273 | 19% |
| WETH (ethereum) | 7.1 | 305,823 | 6% |
| USDC (ethereum) | 6.4 | 278,021 | 6% |
| WCRO (cronos) | 1.3 | 55,604 | 1% |
Two things fall out of this table immediately.
**A single busy asset can dominate a plan.** One continuous SOL price subscription is roughly 2.25M credits a month, about 45% of everything Pro includes. Two such assets is 90%.
**The free tiers are not built for continuous streaming.** At 52 updates a minute, SOL exhausts the 50,000 free allowance in under three days, and the 300,000 registered allowance in about a week. A quiet asset like WCRO would run for months on the same allowance.
The connection caps are separate from your credit allowance and unchanged: 25 subscriptions per connection, 10 concurrent streams per IP. Those limit concurrency, not spend.
## Stream or poll
The honest answer: **streaming saves you credits only when you would otherwise poll faster than the asset changes.** Streaming's real benefit is latency and freshness, not cost.
Polling costs exactly what you choose:
| Poll interval | Credits per asset per 30 days |
| ---------------- | ----------------------------- |
| Every 1 second | 2,592,000 |
| Every 10 seconds | 259,200 |
| Every 60 seconds | 43,200 |
| Every 5 minutes | 8,640 |
Now compare against the measured rates. The crossover is simply whether the asset's update rate is above or below your poll rate:
* **SOL at 52 updates a minute.** Streaming costs 2.25M credits a month. Polling once a minute costs 43,200. Polling is **52 times less**, at the price of being up to 60 seconds stale.
* **WETH on ethereum at 7.1 a minute.** Streaming costs 306k credits. Polling every 10 seconds costs 259k. Roughly break even, and streaming wins on freshness.
* **WCRO at 1.3 a minute.** Streaming costs 56k credits. Polling every 10 seconds costs 259k. **Streaming costs about 5 times less** and is fresher.
The rule of thumb:
* If you need sub second freshness, stream. Nothing else gets you there, and the cost is the cost.
* If a minute of staleness is acceptable and the asset is busy, poll. It costs far less.
* If the asset is quiet, stream. You pay almost nothing and get instant updates.
## Four worked models
### 1. Price ticker on 10 assets
A dashboard streaming 10 mixed assets, say 3 busy and 7 quiet, averaging about 15 updates a minute each:
```
10 assets x 15 updates/min x 60 x 24 x 30 = 6,480,000 credits/month
```
That is over Pro's 5M. Options: drop to the 5 assets users actually watch, or poll the quiet ones and stream only the busy ones.
### 2. Portfolio refresh, 50 tokens, every 5 minutes
Batch endpoints cut latency and per-minute request pressure, not credits: each token still bills as one credit.
```
50 tokens x 288 refreshes/day x 30 = 432,000 credits/month
```
Comfortable on a registered free key. Cache the static half (names, decimals, pool pairs) and you spend nothing extra on it.
### 3. Drain detection on 25 pools
Reserve streams emit per block in which reserves change, which is far less often than prices tick. Assume about 4 updates a minute per pool:
```
25 pools x 4 updates/min x 60 x 24 x 30 = 4,320,000 credits/month
```
Fits Pro with headroom. This is the workload streaming is genuinely built for: you cannot poll your way to catching a one block drain.
### 4. Backfill plus live
Size these separately. A one off historical pull is a fixed cost:
```
500 pools x 365 daily candles / 366 per request = ~500 requests, 500 credits, once
```
Then add the steady state streaming figure from above. Backfill is almost never the expensive half.
## Cutting real time spend without losing freshness
* **Subscribe to fewer, higher signal assets.** Cost scales with what you watch, not with connections. Ten idle assets on one connection still bill for every update.
* **Split "must be live" from "can be stale."** Stream the price your UI shows in real time; cache names, decimals, pool pairs and completed candles. Completed OHLCV candles never change.
* **Filter server side.** `liquidity_usd_min` and the other threshold filters keep pages small so you pull fewer rows overall.
* **Poll the boring ones.** A stablecoin pinned to \$1.00 does not need a live stream.
* **Disconnect when nobody is watching.** A dashboard nobody has open still bills for every update it receives.
## Check your own numbers
Usage is visible in [console.dexpaprika.com](https://console.dexpaprika.com), and `GET /usage` returns the same figures for an authenticated key. Both count credits.
The reliable way to size a workload is to measure it: subscribe to exactly what you intend to run, leave it for ten minutes, and multiply. The table above was produced this way, and a ten minute sample of your own assets will beat any estimate on this page.
## Which plan do I need?
Take your total from the models above, then:
* **Under 50,000 credits a month:** the free tier, no key needed to start.
* **50,000 to 300,000:** register a free key.
* **300,000 to 5M:** [Pro](https://dexpaprika.com/api/pricing), \$99 a month.
* **Over 5M:** Pro plus \$20 per additional 1M, or talk to us about [Enterprise](/api-pro/introduction) if streaming volume is the driver, since Enterprise limits are set to the workload.
If the number lands just over a tier boundary, revisit the list above before upgrading. Dropping three idle assets from a ticker usually costs less than the next plan.
## FAQs
Yes. One request costs one credit, and each update delivered over a stream costs one credit. Connections themselves are free; what you pay for is updates delivered.
Only if the asset updates less often than you would poll. On a busy asset like SOL, streaming can cost 50 times more than polling once a minute. On a quiet asset it costs a fraction. Stream for freshness, poll for thrift.
No. Batch endpoints collapse several round trips into one HTTP call, which cuts latency, connection overhead and pressure on the 30-per-minute request limit, but each token still bills as one credit. Ten tokens cost ten credits either way.
No. Updates are pushed only when a value changes, so an idle market costs nothing. The cost is entirely a function of how often what you subscribed to moves.
# Error handling and troubleshooting
Source: https://docs.dexpaprika.com/knowledge-base/error-handling
Complete guide to DexPaprika API error codes, common issues, and troubleshooting steps for REST API and Streaming API.
## REST API errors
All REST API errors return a JSON object with a `message` field (and sometimes an `error` field). Here's every status code you can encounter:
### 200 Success
Normal response. Parse the JSON body.
One important edge case: **batch pricing** (`GET /networks/{network}/multi/prices`) returns HTTP 200 with an empty array `[]` when none of the requested tokens have pricing data. This is not an error. It means the tokens were processed but none had prices.
***
### 400 Bad Request
The request was malformed or contained invalid parameters. The body names the offending parameter and, where there is a fixed set, lists the accepted values:
```json theme={null}
{"message": "invalid query parameters: order_by (must be one of [volume_usd_24h volume_usd_7d volume_usd_30d liquidity_usd txns_24h created_at price_usd price_change_percentage_24h price_change_percentage_6h price_change_percentage_1h price_change_percentage_5m])"}
```
**Common causes:**
* Invalid `order_by` on `/networks/{network}/pools/search`. The accepted values are `volume_usd_24h`, `volume_usd_7d`, `volume_usd_30d`, `liquidity_usd`, `txns_24h`, `created_at`, `price_usd`, `price_change_percentage_24h`, `price_change_percentage_6h`, `price_change_percentage_1h` and `price_change_percentage_5m`. The older `volume_usd` spelling is rejected
* Invalid `order_by` on `/networks/{network}/tokens/search`, which takes a **different** list: `volume_usd_24h`, `volume_usd_7d`, `volume_usd_30d`, `liquidity_usd`, `txns_24h`, `price_change_percentage_24h`, `created_at` and `fdv_usd`
* Sorting tokens by price. Do not read `price_usd` out of the token 400 body and retry with it. That value appears in the message, and `order_by=price_usd` on either token search path answers with a second `400`. Ordering tokens by raw price is not supported. Sort by `volume_usd_24h` or `liquidity_usd` instead and read `price_usd` off the rows you get back. The eight values listed above are the ones that actually return `200`
* Sorting tokens by a short price change window. The 6h, 1h and 5m windows exist on pool search only, so passing one as `order_by` to either token search endpoint returns a `400`
* Using an unknown parameter **name**. Unknown names are silently dropped rather than rejected, so a request with `sort_by=` or `volume_24h_min=` returns 200 with default ordering and no filter applied. Check the echoed `query` object in the response to see what the API actually used
* Invalid `interval` value for OHLCV (must be one of: `1m`, `5m`, `10m`, `15m`, `30m`, `1h`, `6h`, `12h`, `24h`)
* Batch pricing with more than 10 tokens
* Batch pricing with zero tokens (empty `tokens` parameter)
* Invalid UNIX timestamp format in filter parameters
* Missing required parameters (e.g., `start` for OHLCV)
**What to do:** Check the parameter values against the [API reference](/api-reference/introduction) or [common patterns](/knowledge-base/common-patterns).
***
### 404 Not Found
The requested resource doesn't exist.
```json theme={null}
{"message": "not found"}
```
**Common causes:**
* Invalid network ID (e.g., `eth` instead of `ethereum`, `sol` instead of `solana`)
* Token address doesn't exist on that network
* Pool address doesn't exist on that network
* Typo in the URL path
**What to do:**
1. Verify the network ID by checking `GET /networks`
2. Use `GET /search?query={name}` to find the correct network and address
3. Check that the address format matches the chain (e.g., `0x...` for EVM chains, base58 for Solana)
***
### 410 Gone
The endpoint has been permanently removed. The body names its own replacement:
```json theme={null}
{"code": 410, "message": "endpoint removed", "replacement": "/networks/:network/pools/search"}
```
**Currently applies to (all return `410 Gone`):**
* `GET /pools`: use `GET /pools/search` instead.
* `GET /networks/{network}/pools`: use `GET /networks/{network}/pools/search` instead.
* `GET /networks/{network}/pools/filter`: use `GET /networks/{network}/pools/search` instead.
* `GET /networks/{network}/dexes/{dex}/pools`: use `GET /networks/{network}/pools/search?dex_name={dex}` instead.
* `GET /networks/{network}/tokens/{token_address}/pools`: use `GET /networks/{network}/pools/search?token_address={token_address}` instead.
* `GET /networks/{network}/tokens/filter`: use `GET /networks/{network}/tokens/search` instead.
* `GET /networks/{network}/tokens/top`: use `GET /networks/{network}/tokens/search` instead.
The whole route is removed in each case, not just the specific network, DEX, or token. A nonsense DEX slug on `/networks/{network}/dexes/{dex}/pools` returns the same 410, not a 404, which is how you tell a removed endpoint from a bad identifier.
***
### 402 Payment Required
Your monthly credit allowance is exhausted. This is not a rate limit, and retrying does not help: the allowance only refills when the period rolls over, or when you add credits.
The body is structured and names the next step for your tier:
```json theme={null}
{
"error": "payment_required",
"tier": "keyless",
"message": "Monthly limit reached for unauthenticated use. Register for a free API key to get 300K credits/month.",
"credits": { "limit": 50000, "used": 50000, "remaining": 0 },
"resets_at": "2026-09-01T00:00:00Z"
}
```
`resets_at` is the UTC timestamp when the allowance rolls over. A 402 deliberately carries no `Retry-After` header, which is how you tell it apart from a 429 in code.
**What to do:**
* Keyless and over 50,000: [register a free API key](https://console.dexpaprika.com) for 300,000 credits a month, no card
* On a free key and over 300,000: move to [Pro](https://dexpaprika.com/api/pricing) for 5,000,000
* On Pro: add credits from [pricing](https://dexpaprika.com/api/pricing), and track what you have spent in [console.dexpaprika.com](https://console.dexpaprika.com)
* Either way, size the workload first with [plan your credit usage](/knowledge-base/credit-usage)
***
### 403 Forbidden (wrong host)
Your key is valid, but it is being sent to the wrong base URL: free keys are served on `api.dexpaprika.com`, paid keys on `api-pro.dexpaprika.com`. Endpoints, paths and parameters are identical on both hosts — only the base URL changes.
```json theme={null}
{
"error": "wrong_host",
"tier": "free_registered",
"message": "This API key is on the free plan, but this host serves paid plans only. Send your requests to https://api.dexpaprika.com instead - endpoints, paths and parameters are identical, and your free monthly credits apply there. Update the base URL in your client and retry, or upgrade to a paid plan to use this host.",
"links": {
"api": "https://api.dexpaprika.com",
"docs": "https://docs.dexpaprika.com/api-pro/introduction",
"coupon": "https://dexpaprika.com/api/pricing?coupon=LAUNCH40"
}
}
```
The mirror case — a paid key sent to `api.dexpaprika.com` — returns the same `wrong_host` error pointing at `api-pro.dexpaprika.com`.
**What to do:** update the base URL in your client and retry. Nothing is wrong with the key or the plan.
***
### 429 Too Many Requests
Per-minute request rate exceeded. This is separate from the monthly credit allowance, which returns 402 instead. A 429 carries a `Retry-After` header giving the seconds to wait.
**Free tier limit:** 30 requests a minute, against a monthly allowance of 50,000 credits without a key (300,000 with a free key). Pro allows 300 requests a minute against 5,000,000 credits.
**What to do:**
* Retry after the number of seconds in `Retry-After`
* Cache responses for data that changes rarely (network lists, DEX lists)
* Use batch pricing instead of individual token requests. It does not reduce credits, but it does reduce HTTP calls, which is what the per-minute limit counts
* Use the streaming API for live prices instead of polling
* Consider the [Pro API](/api-pro/introduction) for 300 requests a minute
***
### 500 Internal Server Error
Something went wrong on our side.
**What to do:** Retry with exponential backoff (wait 1s, then 2s, then 4s, etc.). If the error persists, check our [Discord](https://discord.gg/DhJge5TUGM) for status updates or contact support.
***
## Streaming API errors
The streaming API at `https://streaming.dexpaprika.com` can fail in two ways: HTTP errors before the stream starts, or SSE error events during an active stream.
### HTTP errors (before stream starts)
| Status | Meaning |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 200 | Connected successfully, streaming |
| 400 | Bad parameters, unsupported chain, token not found, or one invalid asset in a batch. Body looks like `{"message": "asset not found: ethereum/0xdead... (not found)"}` |
| 429 | Stream limit exceeded. Most often the per-IP cap: the 11th concurrent SSE stream from one IP returns `{"message": "ip stream limit exceeded"}`. The per-route rate limit and the global stream slot pool also return 429 |
**The 400 behavior is strict:** In a POST request with multiple tokens, if even one token is invalid, the entire request fails. Validate all tokens via the REST API before streaming them.
### SSE errors (during active stream)
If something goes wrong during an active stream, the error arrives as an SSE event:
```
event: error
data: {"message": "..."}
```
**What to do:** Close the connection and reconnect with exponential backoff.
***
## Pagination gotchas
Two styles are in play, and mixing them up is the most common migration bug.
**Cursor-based**, which covers every search endpoint (`/networks/{network}/pools/search`, `/pools/search`, `/networks/{network}/tokens/search`, `/tokens/search`): read `has_next_page` and pass `next_cursor` back as `cursor`. There is no `page` parameter and no `page_info` object. Passing `page` here does nothing: unknown query parameters are dropped silently and you get a plausible-looking first page back every time.
**Page-based**, which is now only `GET /networks/{network}/dexes` and `GET /networks/{network}/pools/{pool_address}/transactions`: `page=1` returns the first page and `page=0` is silently treated as `page=1`.
Maximum page size is 100 items (via `limit` parameter) in both styles. Transaction pagination is limited to 100 pages; for deeper history use the `cursor` parameter there too.
***
## Common mistakes and fixes
**Symptom:** `GET /networks/{network}/pools/search` returns `{"results": [], "has_next_page": false}`
**Cause:** Filters are too strict (for example a high `volume_usd_24h_min` on a quiet network), or a very small `limit`.
**Fix:** Loosen the filters, or use `limit=10` or higher. Note the response wraps rows in `results` and pages with `next_cursor`, not `page_info`.
**Symptom:** `GET /search?query=USDC` returns empty arrays.
**Cause:** Search is best-effort and may not match very common/generic terms well. It searches across tokens, pools, and DEXes.
**Fix:** Try more specific queries (e.g., the token address), or use the token endpoint directly if you know the network and address.
**Symptom:** OHLCV request returns `[]`.
**Cause:** The `start` date might be before the pool existed, or the pool may have very low activity in the requested period.
**Fix:** Check the pool's `created_at` field to ensure your date range is valid. Try a broader interval (e.g., `24h` instead of `1h`).
**Symptom:** Code that used `/pools`, `/networks/{network}/pools`, or `/networks/{network}/pools/filter` now returns `410 Gone`.
**Cause:** Those endpoints were removed. The replacement is `GET /networks/{network}/pools/search` (single network) or `GET /pools/search` (across networks).
**Fix:** Repoint to `*/search` and update params and response handling: `volume_24h_min` becomes `volume_usd_24h_min`, `sort_by` becomes `order_by`, `sort_dir` becomes `sort`, and `page` becomes cursor-based (`has_next_page` + `next_cursor`). The response wraps rows in `results`; each row uses `id` (pool address), `volume_usd_24h`, and `transactions_24h`. See the [pool filtering tutorial](/tutorials/pool-filtering).
**Symptom:** Code that used `/networks/{network}/dexes/{dex}/pools` now returns `410 Gone`.
**Cause:** The endpoint was removed. The DEX moved out of the path and into the `dex_name` filter on pool search.
**Fix:** Call `GET /networks/{network}/pools/search?dex_name={dex}`. `dex_name` takes the DEX id, the `dex_id` field from `GET /networks/{network}/dexes`, matched case-insensitively. That is what the old path segment held, so it is the value already in your code. Passing that response's `dex_name` field instead, a display name like `Uniswap V3`, returns HTTP 200 with an empty `results` array rather than an error, so an empty page here usually means a display name went in where an id belonged.
```bash theme={null}
# before
curl "https://api.dexpaprika.com/networks/ethereum/dexes/curve/pools?order_by=volume_usd&sort=desc&limit=10"
# after
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?dex_name=curve&order_by=volume_usd_24h&sort=desc&limit=10"
```
Three response changes bite here. Rows come back under `results`, not `pools`. Pagination is `has_next_page` + `next_cursor` instead of `page` + `page_info`. And the 24h volume field is `volume_usd_24h`, with no bare `volume_usd` on the row; passing the old `order_by=volume_usd` is rejected with a `400` naming the accepted values.
**Symptom:** Streaming connection returns 400 instantly.
**Cause:** One or more assets in the request are invalid (wrong chain ID, non-existent token address).
**Fix:** Validate every token via the REST API before adding it to a streaming request. In a POST batch, all assets must be valid. One bad asset cancels the entire stream.
### FAQs
Not the draft standard `RateLimit-*` family. Every response does carry `X-Api-Plan`, naming the plan the request was evaluated against. For counts, call `GET /usage` or open [console.dexpaprika.com](https://console.dexpaprika.com). See [response headers](/knowledge-base/response-headers).
The per-minute rate frees up within the next minute; after a 429, wait the `Retry-After` seconds and retry. The monthly credit allowance resets at the time given in `resets_at` on a 402.
No. Every endpoint costs one credit per request, with no per-endpoint weights or compute units. The one wrinkle is batch endpoints, which charge one credit per item in the batch.
# Network IDs, addresses, and case sensitivity
Source: https://docs.dexpaprika.com/knowledge-base/identifiers
How to canonicalize network IDs, token addresses, pool addresses, and DEX identifiers when calling the DexPaprika API. Mixed-case inputs return 404 on REST; the MCP layer normalizes.
## Why this matters
When you build code that interpolates user input into API paths, the smallest case mismatch returns a 404 with no helpful explanation. The fix is one line, but you have to know about it.
This page documents which identifiers are case-sensitive, what the canonical form looks like, and what happens at the wire level if you get it wrong.
***
## Network IDs
**Canonical form:** lowercase, underscore-separated.
Examples: `ethereum`, `solana`, `base`, `arbitrum`, `bsc`, `polygon`, `optimism`.
**What the REST API does with non-canonical input:**
```bash theme={null}
curl -o /dev/null -w "%{http_code}\n" "https://api.dexpaprika.com/networks/ETH/pools/search?limit=1"
# 404
curl -o /dev/null -w "%{http_code}\n" "https://api.dexpaprika.com/networks/ethereum/pools/search?limit=1"
# 200
```
`ETH` returns 404. `Ethereum`, `eth`, `Solana` all return 404. Network IDs are strict lowercase canonical at the REST layer.
**What the MCP layer does with non-canonical input:**
The DexPaprika MCP normalizes common synonyms (`eth` → `ethereum`, `sol` → `solana`, `arb` → `arbitrum`, etc.) before calling the REST API. If you are calling the API through the MCP, mixed case and common abbreviations work. If you are calling REST directly, you need to canonicalize yourself.
**Recommended pattern:** always call `GET /networks` once at startup and cache the `id` values. Treat any user-supplied network name as untrusted and map it through your cached list before constructing the URL.
```bash theme={null}
curl -s "https://api.dexpaprika.com/networks" | jq '.[].id'
```
Returns the authoritative list of canonical IDs.
***
## Token and pool addresses
**EVM chains** (Ethereum, BSC, Base, Arbitrum, Polygon, Optimism, Avalanche, and similar): lowercase hex, `0x` prefix, 40 hex characters.
```
0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 ← canonical (lowercase)
0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 ← EIP-55 checksum, not what the API returns
```
The API normalizes checksum addresses internally, so both spellings work for input. The response always uses lowercase.
If your code does string comparison between an address from the API and an address from another source (e.g., a wallet, a block explorer, a contract event), lowercase both sides before comparing.
**Solana**: base58-encoded, case-sensitive. There is no concept of "lowercase Solana address." Pass it through exactly as you received it.
```
So11111111111111111111111111111111111111112 ← wrapped SOL, canonical case
```
**Sui, Aptos**: 0x-prefixed hex, similar to EVM. Lowercase canonical.
**TON**: friendly format (base64url) is case-sensitive. Raw format is hex. Use whichever the explorer shows.
***
## DEX identifiers
**Canonical form:** lowercase, underscore-separated.
```
uniswap_v3
raydium
pancakeswap_v3
curve_finance
balancer_v2
```
DEX IDs are returned by `GET /networks/{network}/dexes` and passed to the `dex_name` filter on `GET /networks/{network}/pools/search`. The old `GET /networks/{network}/dexes/{dex}/pools` path took the id as a path segment; it was removed and returns `410 Gone`.
`dex_name` is the one identifier that is not case-strict. It resolves the id case-insensitively, so `dex_name=curve`, `dex_name=Curve` and `dex_name=CURVE` all return the same rows. It resolves only the id. The DEX list returns `dex_id` and `dex_name` on every row, and despite the matching parameter name it is `dex_id` you pass. Feeding it the `dex_name` field, a display name like `Uniswap V3`, returns HTTP 200 with an empty `results` array rather than an error. The `dex_id` form is also what the removed path segment held, so it is the value already sitting in most existing code.
**One catch:** some commit messages and changelog entries refer to DEXes by their on-chain name (e.g., "CronosV3"), but the public API slug is different (e.g., `vvs_v3` on Cronos). Always use the slug returned by the API, never the marketing name.
```bash theme={null}
# Get the authoritative slug list for any chain:
curl -s "https://api.dexpaprika.com/networks/cronos/dexes" | jq '.dexes[].dex_id'
```
***
## Quick reference
```
Network ID: lowercase, snake_case ethereum, solana, base
EVM address: lowercase hex, 0x-prefix 0xa0b8...eb48
Solana address: base58, case-sensitive So111...1112
DEX ID: lowercase, snake_case uniswap_v3
Pool address: same rules as token (per chain)
```
If you get a 404 and the path looks right at a glance, the first thing to check is case on the network ID and DEX ID. The second thing to check is whether you accidentally URL-encoded part of the path.
For other 404 causes, see [Error handling](/knowledge-base/error-handling).
# Joining token data across chains
Source: https://docs.dexpaprika.com/knowledge-base/joining-across-chains
How to safely aggregate token data across multiple chains. Use (chain, address) tuples as the join key, never the symbol. Bridge variants share symbols but have different addresses.
## The silent-merge problem
If you query DexPaprika for "USDC" across several chains and aggregate by symbol, you will silently merge bridge variants into native USDC. The numbers you compute will be wrong. The API will not warn you. Your dashboard will say "USDC volume: \$X billion" and that number will be a quiet lie.
This page explains why, and what to do instead.
***
## Why symbol is not a safe key
A token's `symbol` is metadata. There is no global registry that prevents two different smart contracts from claiming the same symbol. In practice this happens constantly with stablecoins and major tokens, because every cross-chain bridge issues its own representation under the same human-readable name.
Concrete example. Search for "USDC":
```bash theme={null}
curl -s "https://api.dexpaprika.com/search?query=USDC" | jq '.tokens[] | {chain, id, symbol}' | head -30
```
A real slice of the response today:
```
chain=ethereum id=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 symbol=USDC
chain=solana id=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v symbol=USDC
chain=base id=0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 symbol=USDC
chain=bsc id=0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d symbol=USDC
chain=arbitrum id=0xaf88d065e77c8cc2239327c5edb3a432268e5831 symbol=USDC
chain=avalanche id=0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e symbol=USDC
```
Six different contracts. Six different `id` (address) values. One shared `symbol`.
If you sum `volume_usd` across these by symbol, you produce a number that includes Avalanche-bridged USDC on Avalanche DEXes, Solana-native USDC on Solana DEXes, and Ethereum-native USDC on Ethereum DEXes, all treated as if they were one token. They are not. They are six tokens that happen to share branding.
The same trap exists for `WETH`, `WBTC`, `USDT`, `DAI`, and most other tokens that have been bridged to multiple chains.
***
## The safe pattern: `(chain, address)` tuple
The only stable global identifier for a token is the combination of the chain it lives on and the contract address on that chain. Every DexPaprika response gives you both fields. Use both.
**Pseudocode:**
```python theme={null}
# Wrong:
totals = defaultdict(float)
for token in all_tokens:
totals[token["symbol"]] += token["volume_usd"]
# Right:
totals = defaultdict(float)
for token in all_tokens:
key = (token["chain"], token["id"].lower())
totals[key] += token["volume_usd"]
```
The `(chain, address)` tuple is unique per token contract globally. There is no overlap. Lowercasing the address (for EVM chains) protects against checksum-vs-lowercase comparison bugs. See [Identifiers](/knowledge-base/identifiers) for the case rules per chain.
***
## When you really do want a cross-chain aggregate
Sometimes the bridge-merged number is the one you want. "How much USDC trading happens globally" is a reasonable question. Just make the merge explicit and intentional, not accidental.
**Pattern:** maintain your own mapping of "this address on this chain is part of the same logical token as that address on the other chain." Bridge tokens are a graph, not a set. The graph changes whenever a new chain integration ships.
Sources for that mapping:
* The bridge's own canonical token list (Wormhole, Stargate, LayerZero, Across, Hop, etc.)
* A symbology service that does this work for you (CoinGecko, CoinMarketCap, our sister product CoinPaprika)
* Manual curation for the handful of tokens you actually care about
What you do not do is aggregate by symbol and assume the result is meaningful.
***
## Searching across chains
When you start from a token name and don't know the chains it lives on, use `GET /search`:
```bash theme={null}
curl -s "https://api.dexpaprika.com/search?query=jupiter" | jq '.tokens[] | {chain, id, name, symbol}'
```
The response is grouped by token (not by chain), and each entry includes the chain it lives on. From there you have your `(chain, address)` pair for any follow-up calls.
For details on what fields the search endpoint returns, see [Common patterns -> Pattern 1](/knowledge-base/common-patterns).
***
## Checklist
Before you ship code that aggregates token data across chains:
1. Confirm your join key is `(chain, address)`, not `symbol`.
2. Lowercase the address for EVM chains before comparing or hashing.
3. If you intentionally collapse bridge variants, document the mapping somewhere a teammate will find.
4. Write a unit test with at least two USDC addresses on different chains, asserting they do not collapse unless you explicitly want them to.
# Rate limits and best practices
Source: https://docs.dexpaprika.com/knowledge-base/rate-limits
DexPaprika API rate limits, pagination rules, caching strategies, and performance optimization tips for building efficient applications.
## Rate limits
| Tier | Monthly credits | Requests per minute | Notes |
| ------------------------- | -------------------------- | ------------------- | -------------------------- |
| **Free (no key)** | 50,000 / month per IP | 30 / min | No API key needed to start |
| **Free (registered key)** | 300,000 / month | 30 / min | Free API key, no card |
| **Pro** | 5,000,000 / month included | 300 / min | \$20 per additional 1M |
| **Enterprise** | Agreed per contract | Agreed per contract | Streaming unmetered |
Monthly credit allowances reset each billing month. When you exceed the per-minute rate, requests return HTTP 429; retry after a short pause. When the monthly allowance runs out, requests return HTTP 402.
### Requests and credits
An HTTP call is a **request**. What it spends is a **credit**. One request costs one credit, with no compute units and no per-endpoint weights: a light call and a heavy call cost the same. Credits are the customer-facing unit, and they are what [console.dexpaprika.com](https://console.dexpaprika.com), invoices and the [pricing page](https://dexpaprika.com/api/pricing) count.
Two places the distinction matters. The per-minute limit counts requests, so 30 per minute means 30 HTTP calls per minute. Batch endpoints charge one credit per item, so one HTTP call for 10 tokens is 1 request and 10 credits.
Need more headroom? [Pro](/api-pro/introduction) raises the monthly allowance to 5M credits with per-1M overage, and Enterprise is custom. Plans and checkout are on [pricing](https://dexpaprika.com/api/pricing).
***
## Pagination rules
The search endpoints page with a cursor. `GET /networks/{network}/dexes` and `GET /networks/{network}/pools/{pool_address}/transactions` are the two that still page with numbers.
| Rule | Value |
| -------------------------- | ------------------------------------------------------------------------------- |
| **Cursor endpoints** | All `*/search` paths: read `has_next_page`, send `next_cursor` back as `cursor` |
| **First page** | `page=1` (1-indexed), on the two page-based endpoints only |
| **Page 0 behavior** | Silently treated as page 1 |
| **Max items per page** | 100 (via `limit` parameter) |
| **Default items per page** | Varies by endpoint (typically 10 or 50) |
| **Transaction pages** | Max 100 pages; use `cursor` for deeper history |
***
## Reduce API calls
### Use batch pricing
Instead of making one request per token:
```
GET /networks/ethereum/tokens/0xc02a.../ → 1 request, 1 credit
GET /networks/ethereum/tokens/0xa0b8.../ → 1 request, 1 credit
GET /networks/ethereum/tokens/0x6b17.../ → 1 request, 1 credit
= 3 requests, 3 credits
```
Use batch pricing for up to 10 tokens at once:
```
GET /networks/ethereum/multi/prices?tokens=0xc02a...,0xa0b8...,0x6b17...
= 1 request, billed as 3 credits (one per token)
```
Batching collapses three round-trips into one HTTP call, which cuts latency, connection overhead and pressure on the 30-per-minute request limit. It does not cut your credit spend: each token in the batch bills as one credit, so 10 tokens cost 10 credits whether you fetch them one at a time or together. Batch for speed and for headroom against the per-minute limit, not to save credits.
### Use streaming for real-time data
Sizing a workload before you pick a plan? [Plan your credit usage](/knowledge-base/credit-usage) has measured update rates, four worked cost models, and the point where streaming stops costing less than polling.
If you need live prices, don't poll the REST API in a loop. Open one streaming connection instead:
```bash theme={null}
# Polling (bad): 60 requests/minute for 1 token
while true; do curl ...; sleep 1; done
# Streaming (good): 1 connection, updates pushed only when the price moves
curl -N "https://streaming.dexpaprika.com/sse/prices?method=token_price&chain=ethereum&address=0xc02a..."
```
A single POST `/sse/prices` connection accepts up to **25 assets**. Each IP can hold **10 concurrent SSE streams**; the 11th returns `429 ip stream limit exceeded`.
Streaming is billed the same way as REST: each update delivered over the stream costs one credit. Because updates are pushed only when a swap moves the price, an idle market costs nothing, but a fast-moving one draws on your monthly allowance much as polling would. Budget for the update rate of what you subscribe to, not the number of connections.
### Cache static data
Some data changes rarely and can be cached:
| Data | Cache for |
| ----------------------------------------- | ---------------------------------------- |
| Network list (`/networks`) | 24 hours |
| DEX list (`/networks/{n}/dexes`) | 1 hour |
| Token metadata (name, symbol, decimals) | 24 hours |
| Pool token pair info | 24 hours |
| OHLCV historical data (completed candles) | Forever (completed candles don't change) |
Data that changes frequently and should be fetched fresh:
* Token prices
* Pool volumes and transaction counts
* Recent transactions
* Current OHLCV candle (incomplete)
### Request only what you need
* Use `limit` to control page size. Do not fetch 100 items if you need 5
* Use `order_by` and `sort` to get the most relevant results first
* Use the filter endpoint for targeted queries instead of fetching all pools and filtering client-side
***
## Handle errors gracefully
### Implement exponential backoff
When requests fail, don't retry immediately in a tight loop:
```python theme={null}
import time
import requests
def fetch_with_backoff(url, max_retries=3):
for attempt in range(max_retries):
response = requests.get(url)
if response.status_code == 200:
return response.json()
if response.status_code == 429:
wait = 2 ** attempt # 1s, 2s, 4s
time.sleep(wait)
continue
if response.status_code >= 500:
wait = 2 ** attempt
time.sleep(wait)
continue
# 400, 404, 410 -- don't retry, fix the request
response.raise_for_status()
raise Exception("Max retries exceeded")
```
### Don't retry client errors
* **400**: fix the request parameters
* **404**: verify the network ID and addresses
* **410**: use the replacement endpoint
Only retry on **429** (rate limit) and **500** (server error).
***
## Streaming best practices
### Validate before streaming
The streaming API rejects the entire request if any asset is invalid. Always verify tokens exist via REST first:
```python theme={null}
# Validate each token before streaming
for token in tokens:
resp = requests.get(f"https://api.dexpaprika.com/networks/{token['chain']}/tokens/{token['address']}")
if resp.status_code != 200:
tokens.remove(token) # Remove invalid tokens
```
### Use batched POST for multiple tokens
Instead of opening multiple GET connections:
```
# Bad: 3 connections for 3 tokens
GET /sse/prices?method=token_price&chain=ethereum&address=0xaaa...
GET /sse/prices?method=token_price&chain=ethereum&address=0xbbb...
GET /sse/prices?method=token_price&chain=ethereum&address=0xccc...
```
Use one POST connection:
```bash theme={null}
# Good: 1 connection for up to 25 tokens
POST /sse/prices
[
{"chain": "ethereum", "address": "0xaaa...", "method": "token_price"},
{"chain": "ethereum", "address": "0xbbb...", "method": "token_price"},
{"chain": "ethereum", "address": "0xccc...", "method": "token_price"}
]
```
### Reconnect with backoff
Streaming connections can drop (network issues, server restarts). Always implement auto-reconnection:
```python theme={null}
import time
def stream_with_reconnect(url, payload, max_backoff=60):
backoff = 1
while True:
try:
resp = requests.post(url, json=payload, stream=True,
headers={"Accept": "text/event-stream"})
if resp.status_code != 200:
raise Exception(f"HTTP {resp.status_code}")
backoff = 1 # Reset on successful connection
for line in resp.iter_lines():
if line and line.startswith(b'data:'):
process_event(line)
except Exception as e:
print(f"Disconnected: {e}. Reconnecting in {backoff}s...")
time.sleep(backoff)
backoff = min(backoff * 2, max_backoff)
```
### Parse prices as decimals
The streaming `p` field is a string, not a number. Use decimal parsing to avoid floating-point precision issues:
```python theme={null}
from decimal import Decimal
price = Decimal(data['p']) # Not float(data['p'])
```
***
## Production checklist
* [ ] Cache network and DEX lists
* [ ] Use batch pricing where possible
* [ ] Use streaming instead of polling for live prices
* [ ] Implement exponential backoff for retries
* [ ] Handle all HTTP status codes (200, 400, 404, 410, 429, 500)
* [ ] Parse streaming prices as decimals, not floats
* [ ] Validate tokens before adding to streaming connections
* [ ] Monitor monthly credit spend in [console.dexpaprika.com](https://console.dexpaprika.com) against your tier's allowance (streaming updates included)
* [ ] Consider Pro if approaching your monthly limit
### FAQs
Yes. The free tier allows 30 requests a minute and Pro allows 300, alongside the monthly credit allowance. Exceeding the per-minute rate returns HTTP 429; retry after a short pause.
A request is one HTTP call. A credit is what it spends. One request costs one credit on every plan, with no compute units or per-endpoint multipliers, so the two numbers match except on batch endpoints, where one request charges one credit per item.
Yes. Each price update delivered over an SSE stream costs one credit, the same as a REST call. Updates are pushed only when a swap moves the price, so an idle subscription costs nothing, but an active one draws on your monthly allowance.
Registering for a free API key raises the keyless 50,000 credits a month to 300,000. Beyond that, [Pro](/api-pro/introduction) includes 5,000,000 credits a month with per-1M overage.
# Response headers, caching, and CORS
Source: https://docs.dexpaprika.com/knowledge-base/response-headers
How DexPaprika sets cache-control, what stale-if-error means for your client, current CORS limitations, and the response headers you can rely on.
## Why this matters
The DexPaprika REST API sits behind Cloudflare with caching enabled. The behavior is generally invisible if you are a server-side consumer making fresh requests, but a few headers change what your client should do when the origin is briefly unavailable, and the CORS story is incomplete enough to bite browser-side code.
This page documents the headers you will see today.
***
## Cache-Control
Every successful GET returns a `Cache-Control` header that looks like this:
```
cache-control: public, max-age=60, s-maxage=60, stale-while-revalidate=10, stale-if-error=600
```
Breaking down what each directive means for your client:
| Directive | Meaning |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `public` | The response may be cached by shared caches (CDN, proxies). |
| `max-age=60` | Your client may serve from its own cache for 60 seconds before revalidating. |
| `s-maxage=60` | Shared caches may serve from cache for 60 seconds. |
| `stale-while-revalidate=10` | After max-age expires, caches may serve stale for 10 more seconds while fetching a fresh copy in the background. |
| `stale-if-error=600` | If revalidation fails, caches may keep serving the stale copy for up to 600 seconds (10 minutes) instead of failing the request. |
The practical effect of `stale-if-error=600` is that a brief origin outage does not break your client. If the API is unreachable, your CDN (or a properly configured HTTP cache library) returns the last-good response with `Age: ` indicating staleness. The data is not real-time during the outage, but your application keeps working.
**For agents and SDK consumers:** most HTTP client libraries do not honor `stale-if-error` by default. If your application needs the resilience this directive promises, use a cache layer that implements it (e.g., a CDN in front of your service, or libraries like `cachecontrol` for Python, `node-cache-control` for Node.js).
**For LLM tool calls:** the MCP layer does not currently cache between calls, so `stale-if-error` does not protect MCP-driven flows. The underlying REST cache still helps because Cloudflare serves the stale copy upstream of the MCP.
***
## Cf-Cache-Status
A Cloudflare-specific header that tells you whether your request hit the CDN cache:
```
cf-cache-status: HIT ← served from CDN cache
cf-cache-status: MISS ← served from origin
cf-cache-status: BYPASS ← cache intentionally skipped
cf-cache-status: DYNAMIC ← cache not applicable (e.g., 4xx, 5xx, OPTIONS)
```
This is informational. You do not need to act on it. It is useful when debugging stale data: if you see `HIT` and the data looks old, the cache is doing its job and the response is up to `max-age` seconds out of date.
***
## Age, Last-Modified
`Age` is the number of seconds since the cached response was generated. `Last-Modified` is the absolute timestamp of the underlying resource (when the API determined the snapshot).
```
age: 13
last-modified: Wed, 13 May 2026 13:18:11 GMT
```
If you need to tell your user how fresh the price data is, `Age` is the most direct signal: the data is at most `Age + max-age` seconds old.
***
## CORS
The DexPaprika REST API currently has **limited CORS support**. Server-side code works without any special handling. Browser-side code that calls the API directly will fail the preflight check.
What the API returns today:
```
# OPTIONS preflight:
HTTP/2 204
allow: OPTIONS, GET
vary: Origin, accept-encoding
# Simple GET with Origin header:
HTTP/2 200
cache-control: public, max-age=60, ...
vary: Origin, accept-encoding
```
What is **missing** for browser fetches to work:
* `Access-Control-Allow-Origin`
* `Access-Control-Allow-Methods`
* `Access-Control-Allow-Headers`
Without these, browsers reject the response even though the network call succeeds. This is a known limitation. Work-arounds while it is being addressed:
1. **Server-side proxy.** Run a small worker (Cloudflare Workers, Vercel Edge, Deno Deploy) that fetches from `api.dexpaprika.com` and re-serves with the right CORS headers to your frontend.
2. **DexPaprika MCP.** Connect through `mcp.dexpaprika.com` from an agent runtime that handles the CORS layer for you.
3. **Static export.** If your data refreshes infrequently, fetch at build time and serve as static JSON.
For real-time browser dashboards, options 1 and 2 are the practical paths.
***
## X-Api-Plan
Every response carries the plan the request was evaluated against:
```
x-api-plan: anonymous
```
A keyless request returns `anonymous`. A request authenticated with a key returns that key's plan: `free_registered` for a free API key, `pro` for a Pro subscription, `unlimited` for partner keys. The same value is returned in the body of `GET /usage`, alongside credit counts when the request is authenticated on `api-pro.dexpaprika.com`. Use it to confirm a key is actually being applied: if you set an `Authorization` header and still see `anonymous`, the key is not reaching the billing gate.
***
## h-hap
You will see this header on every response:
```
h-hap: 01
```
It is an internal infrastructure marker. You can ignore it. Future versions of the API may remove it without notice.
***
## Headers not present (today)
Some headers you might expect from a mature API are not currently emitted:
* **`Deprecation` (RFC 9745)** and **`Sunset` (RFC 8594)**: not set. When DexPaprika deprecates an endpoint or field in the future, these will appear with the relevant dates. Until then, deprecation status is documented in the API reference and in the [changelog](/changelog/changelog), not in headers.
* **`RateLimit-*` (draft-ietf-httpapi-ratelimit-headers)**: not set. `X-Api-Plan` is the only billing-related header today. For counts, call `GET /usage`. See [Rate limits](/knowledge-base/rate-limits) for the allowances.
* **`ETag` / `If-None-Match`**: not set. Use `Last-Modified` if you need to track resource freshness.
***
## Quick reference
```
cache-control: public, max-age=60, ..., stale-if-error=600
cf-cache-status: HIT | MISS | BYPASS | DYNAMIC
x-api-plan: anonymous | free_registered | pro | unlimited
age:
last-modified:
vary: Origin, Accept-Encoding
```
All response headers are lowercase per HTTP/2 convention. If you parse them from a library that returns header names verbatim, accept both `Cache-Control` and `cache-control`. Some intermediaries normalize, others do not.
For error responses (4xx, 5xx), most cache-related headers are absent and `cf-cache-status: DYNAMIC` is set. The error envelopes themselves are documented in [Error handling](/knowledge-base/error-handling).
# Skill
Source: https://docs.dexpaprika.com/skill
Query on-chain DEX data and stream real-time crypto prices using DexPaprika -- token prices, liquidity pools, OHLCV history, swap transactions, advanced pool filtering, and live price streaming via SSE across 33+ blockchains. Use this skill whenever the user wants to fetch crypto token prices, look up pool or DEX data, get historical candlestick data, find new pools, filter pools by volume/transactions/age, stream live prices, build real-time dashboards, or build anything that needs on-chain DEX data. Also use when the user mentions DexPaprika, dexpaprika, streaming crypto prices, or SSE price feeds. REST docs: https://docs.dexpaprika.com -- Streaming docs: https://docs.dexpaprika.com/streaming/introduction
# DexPaprika API
Free API for on-chain DEX data and real-time price streaming. 33+ blockchains, 25M+ tokens, 27M+ pools. No API key, no authentication.
| Service | Base URL | Purpose |
| ------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| REST API | `https://api.dexpaprika.com` | Token data, pools, OHLCV, transactions, search |
| Streaming API | `https://streaming.dexpaprika.com` | Real-time price updates via SSE (\~1s intervals). [Full docs](https://docs.dexpaprika.com/streaming/introduction) |
| CLI | `curl -sSL .../install.sh \| sh` | Terminal tool wrapping the full API. Install: `curl -sSL https://raw.githubusercontent.com/coinpaprika/dexpaprika-cli/main/install.sh \| sh` |
All examples use curl, but any HTTP client works. The CLI (`dexpaprika-cli`) wraps every endpoint into simple commands with `--output json --raw` for scripting. REST responses are JSON. Streaming responses are Server-Sent Events.
***
## Core concepts
**Network IDs** are lowercase chain identifiers: `ethereum`, `solana`, `bsc`, `arbitrum`, `base`, `polygon`, `optimism`, `avalanche`, etc. Get the full list from `GET /networks`.
**Token addresses** are the on-chain contract addresses (e.g., `0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2` for WETH on Ethereum, `So11111111111111111111111111111111111111112` for SOL on Solana).
**Pool addresses** are the on-chain liquidity pool contract addresses. Find them via token pools or network pools endpoints.
If you don't know the network or address for a token, use search first.
***
## Endpoints
### Search (start here when you don't have addresses)
```
GET /search?query={query}
```
Search tokens, pools, and DEXes by name, symbol, or address. Case-insensitive. Use this to resolve "what's the ETH price" into the actual network + address you need.
**Example:** Find Jupiter token
```bash theme={null}
curl "https://api.dexpaprika.com/search?query=jupiter"
```
***
### Token price and details
```
GET /networks/{network}/tokens/{token_address}
```
Returns name, symbol, chain, decimals, USD price, fully diluted valuation, liquidity, and volume/transaction stats at multiple time windows (24h, 6h, 1h, 30m, 15m, 5m).
**The price is at** `response.summary.price_usd`
**Example:** Get SOL price
```bash theme={null}
curl "https://api.dexpaprika.com/networks/solana/tokens/So11111111111111111111111111111111111111112"
```
Extract just the price:
```bash theme={null}
curl -s "https://api.dexpaprika.com/networks/solana/tokens/So11111111111111111111111111111111111111112" | jq '.summary.price_usd'
```
***
### Batch token prices
```
GET /networks/{network}/multi/prices?tokens={addr1},{addr2},{addr3}
```
Fetch USD prices for multiple tokens in one request. Comma-separated addresses, max 10 per request. Unknown or unpriced tokens are silently omitted from the response.
**Example:** WETH + USDC on Ethereum
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/multi/prices?tokens=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2,0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
```
Response is an array of `{id, chain, price_usd}` objects. Order is not guaranteed.
***
### Top pools on a network
```
GET /networks/{network}/pools?page={1}&limit={10}&order_by={volume_usd}&sort={desc}
```
List top liquidity pools. Pages are 1-indexed, max 100 per page. Order by: `volume_usd`, `price_usd`, `transactions`, `last_price_change_usd_24h`, `created_at`.
**Example:** Top 5 Ethereum pools by volume
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools?limit=5&order_by=volume_usd&sort=desc"
```
***
### Advanced pool filtering (new)
```
GET /networks/{network}/pools/filter
```
Filter pools with range queries on volume, transactions, and creation date. This is the endpoint to use for building pool screeners, finding high-volume pools, or discovering recently created pools that meet specific criteria.
**Parameters:**
| Parameter | Type | Description |
| ---------------- | ------- | ------------------------------------------------ |
| `page` | integer | Page number, **1-indexed** (default: 1) |
| `limit` | integer | Items per page, 1-100 (default: 50) |
| `volume_24h_min` | number | Min 24h volume in USD |
| `volume_24h_max` | number | Max 24h volume in USD |
| `txns_24h_min` | integer | Min transactions in last 24h |
| `created_after` | integer | UNIX timestamp -- only pools created after this |
| `created_before` | integer | UNIX timestamp -- only pools created before this |
| `sort_by` | string | `volume_24h` (default), `txns_24h`, `created_at` |
| `sort_dir` | string | `asc` or `desc` (default: `desc`) |
Note: `volume_7d_min`, `volume_30d_min`, `liquidity_usd_min`, `liquidity_usd_max` exist in the spec but are not functional yet -- they return empty results.
All filters combine with AND logic. The response includes `page_info` with `total_items` and `total_pages`.
**Example:** High-volume Ethereum pools (>\$100k daily volume)
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/filter?volume_24h_min=100000&sort_by=volume_24h&sort_dir=desc"
```
**Example:** Recently created Solana pools with activity
```bash theme={null}
curl "https://api.dexpaprika.com/networks/solana/pools/filter?created_after=1709251200&txns_24h_min=50&sort_by=created_at&sort_dir=desc"
```
***
### Pool details
```
GET /networks/{network}/pools/{pool_address}?inversed={false}
```
Returns liquidity, reserves, pricing, token pair info, and DEX metadata for a specific pool. Set `inversed=true` to flip the price ratio (token1/token0 instead of token0/token1).
***
### Pool OHLCV (historical candlesticks)
```
GET /networks/{network}/pools/{pool_address}/ohlcv?start={timestamp}&interval={24h}&limit={30}
```
Historical price candlestick data. `start` is required (ISO 8601 or UNIX timestamp). Optional `end` (max 1 year from start).
**Intervals:** `1m`, `5m`, `10m`, `15m`, `30m`, `1h`, `6h`, `12h`, `24h`
**Max data points:** 366 per request
**Example:** Last 30 days of daily candles for a pool
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/ohlcv?start=2025-01-01&interval=24h&limit=30"
```
***
### Pool transactions
```
GET /networks/{network}/pools/{pool_address}/transactions?page={1}&limit={20}
```
Recent swaps, liquidity adds, and removes. Reverse chronological order. Pages 1-indexed, max 100 pages. For deep pagination, use `cursor` parameter (transaction ID) instead of page numbers.
Each transaction includes: token amounts (`amount_0`, `amount_1`), volumes (`volume_0`, `volume_1`), USD prices (`price_0_usd`, `price_1_usd`), token symbols, sender/recipient, and timestamps.
***
### Pools for a token
```
GET /networks/{network}/tokens/{token_address}/pools?order_by={volume_usd}&sort={desc}&limit={10}
```
Find all pools containing a specific token. Optional: add `address` parameter to filter to pools paired with a second specific token. Add `reorder=true` to make the queried token the primary token in all metrics.
***
### DEXes on a network
```
GET /networks/{network}/dexes
```
List all DEXes on a network with their identifiers. Use the `dex` id to filter pools:
```
GET /networks/{network}/dexes/{dex}/pools
```
***
### Networks
```
GET /networks
```
Returns all supported blockchain networks with their IDs. Use these IDs in all other endpoints.
***
### Platform stats
```
GET /stats
```
High-level counts: total networks, DEXes, pools, and tokens. Useful for health checks.
***
## Streaming API -- Real-time prices via SSE
Stream live token prices with \~1 second update intervals. Uses Server-Sent Events (SSE) -- works with any HTTP client that supports streaming. For full streaming docs, guides, and code examples see: [https://docs.dexpaprika.com/streaming/introduction](https://docs.dexpaprika.com/streaming/introduction)
**Base URL:** `https://streaming.dexpaprika.com` (no landing page -- only the `/stream` paths below work)
### Stream a single token (GET)
```bash theme={null}
curl -N "https://streaming.dexpaprika.com/stream?method=t_p&chain=ethereum&address=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
```
### Stream multiple tokens (POST) -- recommended for 2+ tokens
Send a JSON array of assets. Single connection, up to 2,000 tokens.
```bash theme={null}
curl -N -X POST "https://streaming.dexpaprika.com/stream" \
-H "Accept: text/event-stream" \
-H "Content-Type: application/json" \
-d '[
{"chain": "solana", "address": "So11111111111111111111111111111111111111112", "method": "t_p"},
{"chain": "ethereum", "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "method": "t_p"}
]'
```
Each asset object requires `chain` (network ID), `address` (token contract), and `method` (always `t_p` for token price).
### SSE event format
```
data: {"a":"0xc02a...","c":"ethereum","p":"2739.79","t":1769778188,"t_p":1769778187}
event: t_p
```
| Field | Meaning |
| ----- | ------------------------------------------------------- |
| `a` | Token address |
| `c` | Chain ID |
| `p` | Price in USD (string for precision -- parse as decimal) |
| `t` | Server send timestamp (UNIX seconds) |
| `t_p` | Price event timestamp (UNIX seconds) |
Use `now() - t` for network latency, `now() - t_p` for total price data latency.
### Python streaming example
```python theme={null}
import requests, json
assets = [
{"chain": "ethereum", "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "method": "t_p"},
{"chain": "solana", "address": "So11111111111111111111111111111111111111112", "method": "t_p"}
]
r = requests.post("https://streaming.dexpaprika.com/stream",
headers={"Accept": "text/event-stream", "Content-Type": "application/json"},
json=assets, stream=True)
for line in r.iter_lines():
if line and line.startswith(b'data:'):
data = json.loads(line[5:])
print(f"{data['c']} {data['a']}: ${data['p']}")
```
### Streaming constraints
* **Max 2,000 assets** per POST request
* **All assets must be valid** -- one invalid asset cancels the entire stream with HTTP 400
* **Global stream limit:** 10,000 concurrent streams per server
* Validate assets via REST `/search` before streaming
* Use multiple smaller requests (100-500 each) rather than one massive request for better load distribution
* Reconnect with exponential backoff on disconnect
### Streaming errors
| Status | Meaning |
| ------ | ----------------------------------------------------------------------------- |
| 200 | Connected, streaming |
| 400 | Bad params, unsupported chain, asset not found, or one invalid asset in batch |
| 429 | Global stream limit exceeded -- retry with backoff |
Errors during an active stream arrive as SSE events: `event: error` + `data: {"message": "..."}`. Handle both HTTP errors (before stream starts) and SSE errors (during streaming).
***
## Constraints and limits
### REST API
* **Rate limit:** 10,000 requests per day
* **Batch prices:** max 10 tokens per request
* **Pagination:** max 100 items per page
* **OHLCV:** max 366 data points per request, max 1 year range
* **Transactions:** max 100 pages of pagination
* **Pagination:** all endpoints are 1-indexed (page=0 is silently treated as page=1)
### Streaming API
* **Max assets per request:** 2,000
* **Global concurrent streams:** 10,000 per server
* **All assets must be valid** or entire stream is cancelled
***
## Error handling
| Status | Meaning | What to do |
| ------ | ------------------------------------------------------------- | --------------------------------------------------------------------- |
| 200 | Success | Parse JSON response |
| 400 | Bad request (invalid params, too many tokens, bad sort field) | Check parameter values and constraints above |
| 404 | Network, token, or pool not found | Verify the network ID and address; use /search to find correct values |
| 410 | Endpoint deprecated (old /pools) | Use `/networks/{network}/pools` instead |
| 429 | Rate limit exceeded | Back off and retry; consider caching responses |
| 500 | Server error | Retry with backoff |
When a batch price request contains only invalid tokens, you get HTTP 200 with an empty array -- not an error.
***
## Common workflows
### "What's the price of X?"
1. `GET /search?query=X` to find the network and token address
2. `GET /networks/{network}/tokens/{address}` to get price at `.summary.price_usd`
### "Show me the top pools on Ethereum"
1. `GET /networks/ethereum/pools?limit=10&order_by=volume_usd&sort=desc`
### "Find new pools with high volume"
1. `GET /networks/{network}/pools/filter?created_after={unix_timestamp}&volume_24h_min=50000&sort_by=created_at&sort_dir=desc`
### "Get historical price data for a token"
1. Find the token's pools via `GET /networks/{network}/tokens/{address}/pools?order_by=volume_usd&sort=desc&limit=1` (the highest-volume pool is the best source)
2. `GET /networks/{network}/pools/{pool_address}/ohlcv?start={date}&interval=24h&limit=30`
### "Compare prices of multiple tokens"
1. `GET /networks/{network}/multi/prices?tokens={addr1},{addr2},{addr3}`
### "Stream live price updates for a token"
1. `GET https://streaming.dexpaprika.com/stream?method=t_p&chain={network}&address={token_address}`
### "Build a real-time dashboard tracking multiple tokens"
1. Validate tokens exist via REST: `GET /search?query={name}` or `GET /networks/{network}/tokens/{address}`
2. `POST https://streaming.dexpaprika.com/stream` with JSON array of `{chain, address, method: "t_p"}` objects
3. Parse SSE events, read price from `p` field (string -- parse as decimal for precision)
### "Monitor a token's price with alerts"
1. Stream the token via GET or POST to `streaming.dexpaprika.com`
2. Compare each incoming `p` value against your threshold
3. Reconnect with exponential backoff on disconnect
***
## What this skill does NOT cover
* **CoinPaprika centralized exchange data** -- that's a different API (`api.coinpaprika.com`)
* **Trading or swapping** -- DexPaprika is read-only; it does not execute trades
***
## Full documentation
* [REST API reference](https://docs.dexpaprika.com/api-reference/introduction)
* [Streaming API docs](https://docs.dexpaprika.com/streaming/introduction)
* [Tutorials](https://docs.dexpaprika.com/tutorials/tutorial_intro)
# Detect Liquidity Drains in Real Time
Source: https://docs.dexpaprika.com/streaming/guides/detect-liquidity-drains
Build a rug-pull detector that alerts the block liquidity leaves a DEX pool. Free streaming API: no node, no log decoding, no getReserves polling. ~70 lines.
To detect a liquidity drain (a rug pull, an LP exit, a liquidity migration) programmatically, subscribe to a pool's reserves over SSE and alert when the pool's **net USD reserve change in a single block** exceeds a threshold. A good starting bar: more than $10,000 *and* more than 20% of the pool. Swaps can't trigger this signal, because a swap moves one token up and the other down and nets to roughly zero. Only liquidity actually entering or leaving a pool moves the net total. A detector built this way caught a fresh Base pool losing **97.3% of its liquidity ($67k) in one block\*\*, the night it was first switched on.
This guide builds that detector in about 70 lines of JavaScript or Python, watching pools on three chains over one connection. No API key, no node, no event-log decoding, no `getReserves` polling loop.
**The full open-source version of what you'll build here.** A zero-dependency engine, a clone-and-run CLI, and a deployable Cloudflare Worker with a public status page, webhook alerts, and flood protection. Fork it and point it at your own pools. [View source →](https://github.com/coinpaprika/liquidity-radar)
## How detection works
Every block in which a subscribed pool's reserves change, the stream emits a `pool_reserves` event with USD values already computed, including `total_delta_usd`, the signed dollar change of the whole pool in that block.
That one field is the entire trick:
| What happened on-chain | `total_delta_usd` | Why |
| ------------------------------- | ----------------- | --------------------------------------------------------------------------- |
| Swap (any size) | ≈ \$0 | One token's reserve goes up, the other goes down. The dollar values cancel. |
| Liquidity added | Strongly positive | Both tokens' reserves rise together. |
| Liquidity removed / **drained** | Strongly negative | Both tokens' reserves fall together. Nothing cancels it. |
A real capture of a $45,930 swap in the USDC/WETH 0.05% pool: one leg `+$46,015`, the other `-\$45,930`, net `total\_delta\_usd: 84.37\`. The same pool's biggest one-block *liquidity* moves run six figures with nothing on the other side. A drain cannot disguise itself as trading volume.
**Big numbers arrive as strings.** The raw token amounts (`reserve`, `delta`) and `block` are JSON strings, because they exceed JavaScript's `Number.MAX_SAFE_INTEGER`. Parse them with `BigInt` if you need them. For drain detection you don't: the USD fields (`total_reserve_usd`, `total_delta_usd`, `reserve_usd`, `delta_usd`) are regular numbers, pre-computed server-side.
## Step 1: Watch a pool's reserves live
No signup, no key. This streams real reserve changes for the largest USDC/WETH pool on Ethereum, one event per block in which its reserves changed:
```bash theme={null}
curl -N "https://streaming.dexpaprika.com/sse/reserves?method=pool_reserves&chain=ethereum&address=0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640"
```
```text Live capture from production theme={null}
event: pool_reserves
request_id: 0
data: {"chain":"ethereum","pool_id":"0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640","block":"25286203","previous_block":"25286202","tokens":[{"token_id":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","reserve":"19397067871705","delta":"46015585500","price_usd":0.9999897960291432,"reserve_usd":19396869.94,"delta_usd":46015.11},{"token_id":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","reserve":"39923043997983135635229","delta":"-28370314221473922862","price_usd":1618.97,"reserve_usd":64634288.92,"delta_usd":-45930.74}],"total_reserve_usd":84031158.86,"total_delta_usd":84.37,"timestamp":1781084332,"block_timestamp":1781084327}
```
The fields the detector cares about:
* `total_reserve_usd`: the pool's current total liquidity in USD
* `total_delta_usd`: the signed USD change this block (the swap above nets to `84.37`, which is noise)
* `block`: where it happened, for receipts
To follow a **token across every pool it trades in**, use `method=token_reserves` with a token address instead. Those events are one-sided by nature (a large buy also drops reserves), so reserve the word "drain" for pool-mode alerts.
## Step 2: The detector
Watch up to **25 pools on a single connection** by POSTing a JSON array. Events route back via `request_id`, the index of the subscription in your array. Mixing chains in one connection works.
The detection rule, two thresholds that scale together:
* `|total_delta_usd| ≥ MIN_USD`, which ignores dust
* `|total_delta_usd| / reserve_before ≥ MIN_PCT`, which ignores moves the pool barely feels
On a $50M blue-chip pool, 20% means an eight-figure event. On a $100k day-old memecoin pool, a \$20k pull trips it. Same two numbers, self-scaling.
```javascript drain-detector.mjs theme={null}
// drain-detector.mjs: alert when a pool loses a big share of its liquidity
// in one block. Node 18+, no dependencies, no API key.
const POOLS = [
{ chain: "ethereum", address: "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", label: "USDC/WETH 0.05%" },
{ chain: "base", address: "0xb2cc224c1c9fee385f8ad6a55b4d94e92359dc59", label: "WETH/USDC (Aerodrome)" },
{ chain: "solana", address: "8sjV1AqBFvFuADBCQHhotaRq5DFFYSjjg1jMyVWMqXvZ", label: "USDT/USDC (Orca)" },
];
const MIN_USD = 10_000; // ignore moves smaller than this
const MIN_PCT = 0.2; // ...and smaller than this share of the pool
async function watch() {
const res = await fetch("https://streaming.dexpaprika.com/sse/reserves", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(POOLS.map(({ chain, address }) => ({ chain, address, method: "pool_reserves" }))),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
let buffer = "";
for await (const chunk of res.body.pipeThrough(new TextDecoderStream())) {
buffer += chunk;
const frames = buffer.split("\n\n");
buffer = frames.pop(); // keep the partial frame for the next chunk
for (const frame of frames) {
const event = frame.match(/^event: (.*)$/m)?.[1];
const id = frame.match(/^request_id: (.*)$/m)?.[1];
const data = frame.match(/^data: (.*)$/m)?.[1];
if (!data) continue;
if (event === "error") throw new Error(JSON.parse(data).message);
if (event !== "pool_reserves") continue; // skip pings and warnings
const update = JSON.parse(data);
const delta = update.total_delta_usd;
const before = update.total_reserve_usd - delta; // reserve before this block
const pct = before > 0 ? delta / before : 0;
// A swap moves one token up and the other down, so total_delta_usd nets
// to ~0. Only liquidity entering or leaving the pool moves the total.
if (Math.abs(delta) < MIN_USD || Math.abs(pct) < MIN_PCT) continue;
const pool = POOLS[Number(id)] ?? { label: update.pool_id };
const verb = delta < 0 ? "🚨 DRAIN" : "🟢 ADD";
console.log(
`${verb} ${pool.label} on ${update.chain}: ` +
`${delta < 0 ? "-" : "+"}$${Math.abs(delta).toLocaleString("en-US", { maximumFractionDigits: 0 })} ` +
`(${(pct * 100).toFixed(1)}% of the pool) at block ${update.block}`,
);
}
}
}
while (true) {
try {
console.log("connecting…");
await watch();
} catch (err) {
console.error(String(err));
}
await new Promise((r) => setTimeout(r, 5000)); // simple retry; see the production guide
}
```
```python drain_detector.py theme={null}
# drain_detector.py: alert when a pool loses a big share of its liquidity
# in one block. Python 3.8+, `pip install requests`, no API key.
import json
import time
import requests
POOLS = [
{"chain": "ethereum", "address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", "label": "USDC/WETH 0.05%"},
{"chain": "base", "address": "0xb2cc224c1c9fee385f8ad6a55b4d94e92359dc59", "label": "WETH/USDC (Aerodrome)"},
{"chain": "solana", "address": "8sjV1AqBFvFuADBCQHhotaRq5DFFYSjjg1jMyVWMqXvZ", "label": "USDT/USDC (Orca)"},
]
MIN_USD = 10_000 # ignore moves smaller than this
MIN_PCT = 0.20 # ...and smaller than this share of the pool
def watch():
response = requests.post(
"https://streaming.dexpaprika.com/sse/reserves",
json=[{"chain": p["chain"], "address": p["address"], "method": "pool_reserves"} for p in POOLS],
stream=True,
timeout=60,
)
response.raise_for_status()
event_type, request_id = None, None
for raw_line in response.iter_lines(decode_unicode=True):
line = raw_line or ""
if line.startswith("event: "):
event_type = line[7:]
elif line.startswith("request_id: "):
request_id = line[12:]
elif line.startswith("data: "):
if event_type == "error":
raise RuntimeError(json.loads(line[6:])["message"])
if event_type != "pool_reserves": # skip pings and warnings
continue
update = json.loads(line[6:])
delta = update["total_delta_usd"]
before = update["total_reserve_usd"] - delta # reserve before this block
pct = delta / before if before > 0 else 0
# A swap moves one token up and the other down, so total_delta_usd
# nets to ~0. Only liquidity entering or leaving moves the total.
if abs(delta) < MIN_USD or abs(pct) < MIN_PCT:
continue
pool = POOLS[int(request_id)] if request_id and request_id.isdigit() else {"label": update["pool_id"]}
verb = "🚨 DRAIN" if delta < 0 else "🟢 ADD"
print(
f"{verb} {pool['label']} on {update['chain']}: "
f"{'-' if delta < 0 else '+'}${abs(delta):,.0f} "
f"({pct * 100:.1f}% of the pool) at block {update['block']}"
)
while True:
try:
print("connecting…")
watch()
except Exception as err: # noqa: BLE001, simple retry; see the production guide
print(err)
time.sleep(5)
```
Run it (`node drain-detector.mjs` / `python drain_detector.py`). At the default thresholds, alerts are rare by design. A 20% single-block move on a real pool is an event. Output from a live run with thresholds lowered for demonstration:
```text Live run (thresholds lowered to show output) theme={null}
connecting…
🟢 ADD WETH/USDC (Aerodrome) on base: +$429,397 (5.3% of the pool) at block 47239458
🚨 DRAIN WETH/USDC (Aerodrome) on base: -$329,331 (-3.8% of the pool) at block 47239459
🚨 DRAIN WETH/USDC (Aerodrome) on base: -$100,020 (-1.2% of the pool) at block 47239465
🟢 ADD WETH/USDC (Aerodrome) on base: +$97,078 (1.2% of the pool) at block 47239466
```
That add/drain oscillation is real and worth understanding. The thresholds section explains it.
## Step 3: Tune the thresholds
Major pools host [JIT (just-in-time) liquidity](https://dexpaprika.com/glossary/jit-liquidity-just-in-time-liquidity) bots that add and remove six-figure positions around individual swaps. We've measured them moving ±4% of an \$8M pool *every block*, which is exactly the oscillation in the sample output above. Liquidity genuinely moves, so a detector genuinely fires; it's just market plumbing, not an exit. A 20% bar clears that churn with a 5x margin. If you watch pools with heavy JIT activity and want a lower bar, net the deltas over a few consecutive blocks: JIT cycles cancel out, drains don't.
For day-old pools in the $20k to $500k range, the defaults already work: a [rug pull](https://dexpaprika.com/glossary/rug-pull) on a $70k pool is a ~$70k negative delta at \~100% of the pool. If you want earlier partial-exit signals, drop `MIN_USD` to \~\$5k and keep `MIN_PCT` at 20%. On small pools the percentage gate does the real work.
The REST pool search endpoint finds rug-candidates programmatically: pools created in the last week with real activity:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/base/pools/search?created_after=$(($(date +%s) - 604800))&txns_24h_min=50&volume_usd_24h_min=10000&order_by=created_at&sort=desc&limit=25"
```
Feed the resulting pool IDs straight into `POOLS`. One connection covers 25 of them.
## Step 4: Route alerts anywhere
Replace `console.log` with a webhook call and the detector becomes a bot. Discord, for example:
```javascript theme={null}
await fetch(process.env.WEBHOOK_URL, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ content: alertText }),
});
```
Before pointing this at a channel people read, add flood protection: deduplicate on `pool_id + block`, hold a per-pool cooldown, and cap sends per hour, because a multi-block drain fires once per block otherwise. [LiquidityRadar](https://github.com/coinpaprika/liquidity-radar) implements that gate, plus reconnection with backoff, a persistent status page, and one-command Cloudflare deployment, if you'd rather fork than build.
## Limits and errors worth knowing
* **25 subscriptions per POST connection, 10 concurrent streams per IP.** Chunk bigger watchlists across connections.
* **One invalid entry rejects the whole multiplexed connection.** The stream sends an `error` event naming the bad asset (`"pool not found: …"`), then closes. Validate addresses before subscribing.
* **`error` events are not all equal**: subscription problems (`asset not found`, `unsupported chain`) are permanent: fix the input, don't retry. Capacity problems (`ip stream limit exceeded`) clear on their own, so retry those with backoff.
* A `ping` arrives every \~15s; if nothing arrives for 30s+, reconnect. See [error handling](/knowledge-base/error-handling) for the full catalog.
## FAQ
Not by swapping: swaps net to \~zero in `total_delta_usd` regardless of size. An exit could in principle drip liquidity out in many small blocks below your thresholds, which is why percentage-of-pool matters more than absolute USD: 100 blocks of 1% exits still cross a 20% net threshold if you accumulate deltas over a window.
Polling misses everything between polls. A pool can drain in one block, and a 30-second loop finds out after the money is gone. Decoding `Sync`/`Mint`/`Burn` logs over a WebSocket node connection works but costs a node provider subscription and per-DEX decoding logic. The reserve stream pushes per-block deltas with USD values pre-computed, for every major DEX shape (Uniswap v2/v3/v4, Curve, Aerodrome, Raydium, Orca…), with no key.
No API key and no credit card to start. The limits are the catch, and there are three: 25 subscriptions per connection, 10 concurrent streams per IP, and each update delivered costs one credit against your monthly allowance. That allowance is 50,000 credits a month keyless, 300,000 with a free key, and 5M on [Pro at \$99 a month](https://dexpaprika.com/api/pricing). A quiet market costs almost nothing; a fast-moving pair draws down credits much like polling would. See [rate limits](/knowledge-base/rate-limits).
The same code in this guide watches Ethereum, Base, and Solana on one connection. Any chain DexPaprika indexes works, see [networks](https://api.dexpaprika.com/networks).
## Next steps
The full feature guide: both subscription methods, payload schemas, limits.
Prices and reserves from zero in 10 minutes.
The POST /sse/reserves endpoint in full detail.
The production version: deployable worker, status page, alert gate.
***
## Get support
Connect with our community and get real-time support.
Share your experience and help us improve.
# Production-Grade Reserve Streaming
Source: https://docs.dexpaprika.com/streaming/guides/production-reserve-streaming
Run /sse/reserves 24/7 without losing data or hammering the API: reconnection that actually works, missed-block detection, error classification, and multiplexing at scale.
Running a reserve stream in production comes down to five rules: trust events, not HTTP status codes; watch the heartbeat; know that state self-heals but ledgers don't; classify errors before retrying; and multiplex deliberately. This guide covers all five with tested code, drawn from running a 58-pool monitor against `/sse/reserves` around the clock. One lesson cost us a night of data: treating a transient "stream limit exceeded" error as fatal silenced 8 pools until morning. The patterns below exist so you don't repeat it.
If you haven't streamed reserves before, start with the [drain detection guide](/streaming/guides/detect-liquidity-drains). This one assumes you have a working consumer and need it to survive real networks, real restarts, and real error states.
## The five rules
| Rule | Why it exists |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Reset backoff only after a parsed event | Proxies can return HTTP 200 and close instantly. Resetting on 200 turns backoff into a 1-second hammer. |
| Treat 30s of silence as a dead connection | Pings arrive every \~15s. A socket can die without an error ever surfacing. |
| State self-heals, ledgers don't | Every event carries absolute reserves. After a gap you're current again, but the per-block change log has a hole. Detect it with `previous_block`. |
| Classify errors before retrying | "Pool not found" never fixes itself. "Stream limit exceeded" always does. Retrying the first wastes a connection slot forever; abandoning the second loses a working subscription. |
| Multiplex deliberately | 25 subscriptions per connection, 10 connections per IP, and one invalid entry rejects the whole batch. |
## Rule 1: Trust events, not status codes
A reconnect loop usually looks like: connect, reset backoff, consume, on failure wait and retry with doubled delay. The subtle bug is *where* the backoff resets. If it resets when the HTTP response arrives, any failure mode that returns `200 OK` and then closes (load balancer deploys, buffering proxies, overload shedding) defeats the exponential entirely: every cycle reconnects at the floor delay, forever, from every client you've shipped.
Reset backoff only after the first parsed SSE frame. A `ping` counts; it proves the stream actually streams.
```javascript theme={null}
let sawEvent = false;
for await (const frame of frames) {
sawEvent = true; // proof the stream works, not just HTTP 200
// ... handle the frame
}
// in the outer loop:
if (sawEvent) backoff = 1_000;
backoff = Math.min(backoff * 2, 30_000);
await sleep(backoff + Math.random() * 1_000); // jitter spreads reconnect storms
```
## Rule 2: Watch the heartbeat
The server sends a `ping` every \~15 seconds. If nothing (events or pings) arrives for 30 seconds, assume the socket is dead even though no error fired, and reconnect. In JavaScript that's a small watchdog timer that aborts the fetch. In Python, `requests` gives you this for free: set the read timeout to 30 seconds and the staleness check becomes an exception your retry loop already handles.
```python theme={null}
response = requests.post(url, json=subs, stream=True, timeout=(10, 30))
# 10s to connect, and any 30s silence raises ReadTimeout: your reconnect path
```
## Rule 3: State self-heals, ledgers don't
This is the property that makes reserve streaming forgiving in production. Every `pool_reserves` event carries **absolute values**: `total_reserve_usd` is the pool's liquidity right now, not a cumulative sum you must maintain. Disconnect for ten minutes, reconnect, and your first event makes your state current again. There is nothing to resync and no REST snapshot to fetch.
What does *not* self-heal is the change log. If you aggregate deltas over time (net flow over a window, cumulative volume of liquidity changes), the blocks you missed are holes in that ledger. The stream gives you exactly what you need to know when that happened: each event's `previous_block` names the last block in which the pool's reserves changed. If it doesn't match the `block` of the previous event you received, you missed something.
```javascript theme={null}
const lastBlock = new Map();
function checkContinuity(idx, update) {
const prev = lastBlock.get(idx);
if (prev !== undefined && update.previous_block !== prev) {
// a hole in the ledger: reset windowed aggregations for this subscription
resetWindows(idx);
}
lastBlock.set(idx, update.block);
}
```
`previous_block` chains event to event, not block to block. Pools only emit
when reserves change, so consecutive events can legitimately be many chain
blocks apart while `previous_block` still matches perfectly. A mismatch
means missed *events*, not quiet markets.
If you need the missed changes themselves, the [pool transactions endpoint](/api-reference/pools/get-transactions-of-a-pool-on-a-network-paging-can-be-used-up-to-100-pages) backfills the gap from REST.
## Rule 4: Classify errors before retrying
Two error channels exist, and both carry a mix of permanent and transient conditions.
**Before the stream starts**, a bad request gets an HTTP error with a JSON body. **Mid-stream**, the server sends an `error` event and closes the connection. Either way, read the message and split:
| Error | Channel | Class | Correct response |
| --------------------------------------- | ------------------------- | --------- | ------------------------------------------------- |
| `unsupported chain: …` | HTTP 400 or `error` event | Permanent | Fix the subscription. Retrying can never succeed. |
| `pool not found: …` / `asset not found` | HTTP 400 or `error` event | Permanent | Same. The message names the bad entry. |
| `too many subscriptions` | HTTP 400 | Permanent | Your batch exceeds 25. Chunk it. |
| `ip stream limit exceeded` | HTTP 429 or `error` event | Transient | Back off and retry. A slot will free up. |
| 5xx, network failures, timeouts | HTTP / socket | Transient | Back off and retry. |
A practical classifier needs one line:
```javascript theme={null}
const isRetryable = (message) => /limit|rate|too many|capacity|timeout|temporar/i.test(message);
```
Getting this wrong in either direction hurts. Retrying a permanent error burns one of your 10 connection slots on a request that can never succeed. Treating a transient error as permanent is how we lost 8 pools for a night: the limit cleared within minutes, but the code had already given up.
**One invalid entry rejects the whole multiplexed connection.** The server
sends a single `error` event naming the bad asset, then closes, and your
other 24 subscriptions go down with it. Validate addresses against the
[REST API](/api-reference/pools/get-a-pool-on-a-network) before
subscribing, and treat "not found" errors as a signal to drop the named
entry and resubscribe the rest.
## Rule 5: Multiplex deliberately
The limits: **25 subscriptions per POST connection, 10 concurrent streams per IP**. That's 250 pools from one machine, if you chunk correctly. Those caps bound concurrency, not spend: every event delivered costs one credit, and 250 pools streaming around the clock runs well past the 5M credits a month [Pro includes](https://dexpaprika.com/api/pricing), so size the workload before you scale the chunks.
* Route events to subscriptions with `request_id`: it's the index of the entry in your POSTed array.
* Keep a fallback route on `(chain, pool_id/token_id)` for defensive code; both fields are in every payload.
* Chunk watchlists into groups of 25 and run one consumer loop per chunk. Let chunks fail independently: a permanent error in one chunk shouldn't stop the other nine.
* Mixing chains and both methods (`pool_reserves`, `token_reserves`) in one connection works fine.
## The full client
Both versions implement all five rules and were run against production as written. Bad-address handling verified live: the client exits with the server's message instead of looping.
```javascript resilient-stream.mjs theme={null}
// resilient-stream.mjs: production-grade reserve streaming. Node 18+, no deps.
// Reconnects with real backoff, detects missed blocks, classifies errors,
// and watches its own heartbeat.
const SUBSCRIPTIONS = [
{ chain: "base", address: "0xb2cc224c1c9fee385f8ad6a55b4d94e92359dc59", method: "pool_reserves" },
{ chain: "solana", address: "8sjV1AqBFvFuADBCQHhotaRq5DFFYSjjg1jMyVWMqXvZ", method: "pool_reserves" },
];
const STALE_MS = 30_000; // two missed pings = reconnect
const MAX_BACKOFF_MS = 30_000;
// Capacity errors clear on their own; subscription errors never will.
function isRetryable(message) {
return /limit|rate|too many|capacity|timeout|temporar/i.test(message);
}
const lastBlock = new Map(); // subscription index -> last seen block
function handleEvent(idx, update) {
// Absolute state: total_reserve_usd is the pool's liquidity NOW, so state
// self-heals after any gap. Only the per-block ledger can have holes.
const prev = lastBlock.get(idx);
if (prev !== undefined && update.previous_block !== prev) {
console.warn(
`[gap] sub ${idx}: missed blocks ${prev} -> ${update.previous_block}. ` +
"Reset any delta-window aggregation for this subscription.",
);
}
lastBlock.set(idx, update.block);
console.log(
`sub ${idx} block ${update.block}: $${Math.round(update.total_reserve_usd).toLocaleString("en-US")} ` +
`(${update.total_delta_usd >= 0 ? "+" : "-"}$${Math.abs(update.total_delta_usd).toFixed(2)})`,
);
}
async function connectOnce(signal) {
const res = await fetch("https://streaming.dexpaprika.com/sse/reserves", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(SUBSCRIPTIONS),
signal,
});
if (!res.ok) {
const body = await res.text();
// 4xx other than 408/429 means the request itself is wrong: do not retry.
const fatal = res.status >= 400 && res.status < 500 && ![408, 429].includes(res.status);
throw Object.assign(new Error(`${res.status}: ${body}`), { fatal });
}
let sawEvent = false;
let lastActivity = Date.now();
const controller = new AbortController();
signal.addEventListener("abort", () => controller.abort(), { once: true });
const watchdog = setInterval(() => {
if (Date.now() - lastActivity > STALE_MS) {
console.warn("[watchdog] no events or pings for 30s, forcing reconnect");
controller.abort(); // tears down the fetch, the outer loop reconnects
}
}, 5_000);
try {
let buffer = "";
for await (const chunk of res.body.pipeThrough(new TextDecoderStream())) {
lastActivity = Date.now();
buffer += chunk;
const frames = buffer.split("\n\n");
buffer = frames.pop();
for (const frame of frames) {
const event = frame.match(/^event: (.*)$/m)?.[1];
const id = frame.match(/^request_id: (.*)$/m)?.[1];
const data = frame.match(/^data: (.*)$/m)?.[1];
if (!data) continue;
sawEvent = true; // proof the stream works, not just HTTP 200
if (event === "error") {
const message = JSON.parse(data).message ?? data;
throw Object.assign(new Error(message), { fatal: !isRetryable(message) });
}
if (event === "warning") {
console.warn(`[server] ${JSON.parse(data).message ?? data}`);
continue;
}
if (event === "ping") continue; // counts as activity, nothing else
if (event === "pool_reserves" || event === "token_reserves") {
handleEvent(Number(id), JSON.parse(data));
}
}
}
} finally {
clearInterval(watchdog);
}
return sawEvent;
}
let backoff = 1_000;
while (true) {
const session = new AbortController();
try {
console.log("connecting…");
const healthy = await connectOnce(session.signal);
if (healthy) backoff = 1_000; // reset only after a proven-working stream
} catch (err) {
if (err.fatal) {
console.error(`fatal, fix the subscription list: ${err.message}`);
process.exit(1);
}
console.error(`transient: ${err.message}`);
}
await new Promise((r) => setTimeout(r, backoff + Math.random() * 1_000)); // jitter
backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
}
```
```python resilient_stream.py theme={null}
# resilient_stream.py: production-grade reserve streaming. Python 3.8+,
# `pip install requests`. Reconnects with real backoff, detects missed
# blocks, classifies errors, and uses the read timeout as a staleness watchdog.
import json
import random
import re
import sys
import time
import requests
SUBSCRIPTIONS = [
{"chain": "base", "address": "0xb2cc224c1c9fee385f8ad6a55b4d94e92359dc59", "method": "pool_reserves"},
{"chain": "solana", "address": "8sjV1AqBFvFuADBCQHhotaRq5DFFYSjjg1jMyVWMqXvZ", "method": "pool_reserves"},
]
STALE_SECONDS = 30 # two missed pings = the read timeout forces a reconnect
MAX_BACKOFF = 30.0
last_block = {} # subscription index -> last seen block
def is_retryable(message: str) -> bool:
# Capacity errors clear on their own; subscription errors never will.
return bool(re.search(r"limit|rate|too many|capacity|timeout|temporar", message, re.I))
class FatalStreamError(Exception):
pass
def handle_event(idx: int, update: dict) -> None:
# Absolute state: total_reserve_usd is the pool's liquidity NOW, so state
# self-heals after any gap. Only the per-block ledger can have holes.
prev = last_block.get(idx)
if prev is not None and update["previous_block"] != prev:
print(
f"[gap] sub {idx}: missed blocks {prev} -> {update['previous_block']}. "
"Reset any delta-window aggregation for this subscription."
)
last_block[idx] = update["block"]
delta = update["total_delta_usd"]
print(
f"sub {idx} block {update['block']}: "
f"${update['total_reserve_usd']:,.0f} ({'+' if delta >= 0 else '-'}${abs(delta):.2f})"
)
def connect_once() -> bool:
response = requests.post(
"https://streaming.dexpaprika.com/sse/reserves",
json=SUBSCRIPTIONS,
stream=True,
# read timeout doubles as the staleness watchdog: pings arrive every
# ~15s, so 30s of silence raises and the outer loop reconnects
timeout=(10, STALE_SECONDS),
)
if not response.ok:
fatal = 400 <= response.status_code < 500 and response.status_code not in (408, 429)
message = f"{response.status_code}: {response.text[:200]}"
if fatal:
raise FatalStreamError(message)
raise RuntimeError(message)
saw_event = False
event_type, request_id = None, None
for raw_line in response.iter_lines(decode_unicode=True):
line = raw_line or ""
if line.startswith("event: "):
event_type = line[7:]
elif line.startswith("request_id: "):
request_id = line[12:]
elif line.startswith("data: "):
saw_event = True # proof the stream works, not just HTTP 200
if event_type == "error":
message = json.loads(line[6:]).get("message", line[6:])
if is_retryable(message):
raise RuntimeError(message)
raise FatalStreamError(message)
if event_type == "warning":
print(f"[server] {json.loads(line[6:]).get('message', '')}")
elif event_type in ("pool_reserves", "token_reserves"):
handle_event(int(request_id or -1), json.loads(line[6:]))
# pings count as activity (they reset the read timeout) and need
# no handling of their own
return saw_event
backoff = 1.0
while True:
try:
print("connecting…")
if connect_once():
backoff = 1.0 # reset only after a proven-working stream
except FatalStreamError as err:
print(f"fatal, fix the subscription list: {err}")
sys.exit(1)
except Exception as err: # noqa: BLE001, includes the staleness timeout
print(f"transient: {err}")
time.sleep(backoff + random.random()) # jitter
backoff = min(backoff * 2, MAX_BACKOFF)
```
**Big numbers arrive as strings.** The raw amounts (`reserve`, `delta`) and
`block` are JSON strings because they exceed `Number.MAX_SAFE_INTEGER`. Use
`BigInt` (or Python's native ints, which handle them automatically) if you
work with raw amounts. The USD fields are regular floats, computed
server-side, and are all you need for most monitoring.
## Don't alert on plumbing
If your production consumer feeds alerts, remember that liquidity moves for boring reasons too. JIT liquidity bots on major pools add and remove six-figure positions around single swaps; we've measured ±4% of an \$8M pool every block. Threshold accordingly, or net deltas over a window so the add/remove cycles cancel. The [drain detection guide](/streaming/guides/detect-liquidity-drains) covers thresholds that survive this.
## Monitor the monitor
A streaming consumer that silently stops is worse than one that crashes. The minimum viable health surface, all derivable from this guide's client:
* **Last event age**: if `lastActivity` is older than a minute, something is wrong even if the process is alive.
* **Per-chunk status**: which subscriptions are live, which died fatally and why (keep the server's error message; it names the bad entry).
* **Gap count**: how many ledger holes you've detected since start.
* **Credit spend**: a busy watchlist bills one credit per delivered event and can work through a monthly allowance quickly. Per-key usage is in [console.dexpaprika.com](https://console.dexpaprika.com).
For a worked example, [LiquidityRadar](https://github.com/coinpaprika/liquidity-radar) (open source) runs this exact stack as a Cloudflare Worker with a public status page showing event freshness, per-subscription failures, and every alert it gated.
## FAQ
Not for state. Every event carries absolute reserve values, so your first event after reconnecting makes you current. You only need REST backfill if you maintain a gapless per-block ledger; detect the holes by comparing each event's previous\_block to the last block you saw.
No. The stream has no replay; a reconnect starts fresh from the next change. That's why the absolute-values property matters: it makes resume-from-now safe for state tracking.
250: 25 subscriptions per POST connection, 10 concurrent streams per IP. Beyond that, distribute across machines or egress IPs.
The whole connection is rejected with one error event naming the bad asset. Drop that entry and resubscribe the rest. Validating addresses against REST before subscribing avoids the round trip.
## Next steps
The detection logic this guide keeps alive: thresholds, JIT noise, alerts.
Payload schemas, both subscription methods, limits.
The complete error catalog for REST and streaming.
All five rules as a deployable, open-source Cloudflare Worker.
***
## Get support
Connect with our community and get real-time support.
Share your experience and help us improve.
# React & Next.js Integration
Source: https://docs.dexpaprika.com/streaming/guides/react-integration
Build real-time price components for React and Next.js applications with TypeScript support
**See React integration in production!** Our live dashboard is built with React, TypeScript, and uses the patterns described in this guide. Streams 6 cryptocurrencies using POST /sse/prices with custom hooks, real-time UI updates, and proper error handling. Open the dashboard and inspect the Network tab to see SSE in action! [View Source →](https://dexpaprika.com/streaming/solana)
## Integration overview
Learn how to integrate DexPaprika's streaming API into React and Next.js applications with:
* Custom React hooks for streaming
* TypeScript support
* Server-side rendering considerations
* Production-ready error handling
***
## Finding Token Addresses
Before streaming, you need token addresses. Use the REST API:
Use the [Search API](/api-reference/search/search-for-tokens-pools-and-dexes) to find tokens by name or symbol
Use the [Networks API](/api-reference/networks/get-a-list-of-available-blockchain-networks) to get supported chains
```bash theme={null}
# Search for USDC tokens
curl "https://api.dexpaprika.com/search?query=USDC"
# Get all supported networks
curl "https://api.dexpaprika.com/networks"
```
***
## Quick Example
```tsx React Hook theme={null}
import { useEffect, useState } from 'react';
function useCryptoPrice(chain: string, address: string) {
const [price, setPrice] = useState(null);
const [status, setStatus] = useState<'connecting' | 'connected' | 'error'>('connecting');
useEffect(() => {
const url = new URL('https://streaming.dexpaprika.com/sse/prices');
url.searchParams.set('method', 'token_price');
url.searchParams.set('chain', chain);
url.searchParams.set('address', address);
const eventSource = new EventSource(url.toString());
eventSource.onopen = () => setStatus('connected');
eventSource.addEventListener('token_price', (event) => {
const data = JSON.parse(event.data);
setPrice(parseFloat(data.price));
});
eventSource.onerror = () => setStatus('error');
return () => eventSource.close();
}, [chain, address]);
return { price, status };
}
// Usage
function PriceDisplay() {
const { price, status } = useCryptoPrice('ethereum', '0xc02aa...');
return (
);
}, (prevProps, nextProps) => {
// Only re-render if price changes by more than 0.01%
return Math.abs(prevProps.price - nextProps.price) / prevProps.price < 0.0001;
});
```
***
## Testing
### Unit Testing with Jest
```typescript theme={null}
// __tests__/useCryptoStream.test.ts
import { renderHook, act } from '@testing-library/react-hooks';
import { useCryptoStream } from '../hooks/useCryptoStream';
// Mock EventSource
global.EventSource = jest.fn(() => ({
addEventListener: jest.fn(),
close: jest.fn(),
onopen: jest.fn(),
onerror: jest.fn(),
})) as any;
describe('useCryptoStream', () => {
it('should connect on mount', () => {
const { result } = renderHook(() =>
useCryptoStream({
chain: 'ethereum',
address: '0xtest',
})
);
expect(result.current.status).toBe('connecting');
});
it('should update price on event', () => {
const { result } = renderHook(() =>
useCryptoStream({
chain: 'ethereum',
address: '0xtest',
})
);
act(() => {
// Simulate price update
const event = new MessageEvent('token_price', {
data: JSON.stringify({
address: '0xtest',
chain: 'ethereum',
price: '1234.56',
timestamp: Math.floor(Date.now() / 1000),
timestamp_price: Math.floor(Date.now() / 1000),
token_price: Math.floor(Date.now() / 1000),
}),
});
// Trigger event handler
const eventSource = (global.EventSource as jest.Mock).mock.results[0].value;
eventSource.addEventListener.mock.calls[0][1](event);
});
expect(result.current.price).toBe(1234.56);
});
});
```
***
## Production Checklist
Wrap streaming components in error boundaries to handle failures gracefully:
```tsx theme={null}
import { ErrorBoundary } from 'react-error-boundary';
Price unavailable}>
```
Always clean up EventSource connections and timeouts:
```tsx theme={null}
useEffect(() => {
const eventSource = new EventSource(url);
// Cleanup function
return () => {
eventSource.close();
clearTimeout(reconnectTimeout);
};
}, []);
```
Monitor connection status and alert on failures:
```tsx theme={null}
useEffect(() => {
if (status === 'error') {
// Send to error tracking service
Sentry.captureException(new Error('Stream connection failed'));
}
}, [status]);
```
Consider lazy loading streaming components:
```tsx theme={null}
const LivePrices = lazy(() => import('./LivePrices'));
```
***
## Get Support
Get help from other React developers.
Report bugs or request features.
# Real-time DEX streaming API: live token prices via SSE
Source: https://docs.dexpaprika.com/streaming/introduction
Stream live DEX token prices and pool reserve changes over Server-Sent Events (SSE). Real-time crypto data with no API key needed to start and no WebSocket plumbing.
**Looking for the full API spec?** Jump to the [Streaming Reference](/streaming/stream-real-time-data-for-a-single-asset) tab for every endpoint, parameter, response schema, and a live API playground.
**Live Demo:** Watch our interactive dashboard stream real-time prices for 6 cryptocurrencies across Ethereum, Solana, and BSC. See connection status, latency metrics, and live updates in action. [Open Live Dashboard →](https://dexpaprika.com/streaming/solana)
## Overview
DexPaprika's Streaming API delivers two real-time feeds via Server-Sent Events (SSE): live token prices and live pool reserve updates. Updates are pushed the moment a swap settles on-chain, with no polling and no API key needed to start.
Start streaming with no API key. A [free key from the console](https://console.dexpaprika.com) raises the monthly credit allowance to 300,000.
A price event is pushed when a swap moves the price, so the rate tracks trading activity. Reserve events land for each block in which a subscribed pool's reserves changed.
Standard Server-Sent Events for easy integration with any modern platform.
Stream tokens and pools from Ethereum, Solana, BSC, Arbitrum, Base, and more networks.
### Two feeds, one transport
USD price updates per token, pushed when a swap moves the price. Measured rates run from about 1 to about 52 updates a minute depending on the asset; see [plan your credit usage](/knowledge-base/credit-usage). Use this for tickers, portfolios, price-alert systems, and dashboards.
Block-level pool reserve deltas, USD-denominated, for any DEX pool or any token. Use this for liquidity monitoring, TVL dashboards, slippage estimation, and MEV/arbitrage detection. **[Learn more →](/streaming/reserves-streaming)**
***
## Why Use Streaming?
### Streaming vs Traditional Polling
```javascript theme={null}
// Efficient: Server pushes updates only when prices change
const url = 'https://streaming.dexpaprika.com/sse/prices?method=token_price&chain=ethereum&address=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
const eventSource = new EventSource(url);
eventSource.addEventListener('token_price', (event) => {
const data = JSON.parse(event.data);
updatePrice(data.price);
});
```
**Benefits:**
* Single persistent connection
* Updates pushed immediately when available
* Minimal bandwidth usage
* Nothing to poll, so no per-minute request limit to manage. Each update delivered still costs one credit, so budget for the update rate of what you subscribe to. See [rate limits](/knowledge-base/rate-limits) and [plan your credit usage](/knowledge-base/credit-usage) for measured rates and worked costs.
```javascript theme={null}
// Inefficient: Constant requests even when prices don't change
setInterval(async () => {
const response = await fetch('https://api.dexpaprika.com/networks/ethereum/tokens/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2');
const data = await response.json();
updatePrice(data.summary.price_usd);
}, 1000); // 86,400 requests per day per asset!
```
**Drawbacks:**
* High server load from repeated requests
* Wasted bandwidth when prices don't change
* Potential rate limiting issues
* Higher latency for updates
***
## Use Cases
### What You Can Build
Display live prices, volume, and market metrics with sub-second latency. **[See live example →](https://dexpaprika.com/streaming/solana)**
Show real-time portfolio values and P\&L as prices fluctuate.
Trigger instant notifications when prices hit target levels.
Compare prices across chains for arbitrage opportunities.
Create live price tickers and widgets for websites.
Stream data into time-series databases for real-time analytics.
***
## Available Endpoints
Each feed has a single-asset GET form and a multi-asset POST form:
### Token price streams (`/sse/prices`)
Stream one token's price per connection. Pass `method`, `chain`, and `address` as query params.
Stream multiple tokens (up to 25 per connection) by posting a JSON array of `{chain, address, method}` entries.
### Pool reserve streams (`/sse/reserves`)
Stream reserve updates for a single pool (`method=pool_reserves`) or a single token across every pool it sits in (`method=token_reserves`).
Subscribe to up to 25 pools or tokens in one connection. Mix `pool_reserves` and `token_reserves` entries freely.
The legacy `/stream` path is still accepted but **deprecated**. It now emits a `warning` event on connect (`"stream is deprecated, move to /sse/prices instead"`) and will be removed in a future release. The legacy `/reserves/stream` path has already been retired; use `/sse/reserves`.
### Quick Comparison
| Feature | GET (Single) | POST (Multi) |
| ----------------------------- | ------------------------------ | --------------------------------------------- |
| **Subscriptions per request** | 1 | Up to 25 |
| **Best for** | Individual asset/pool tracking | Portfolios, dashboards, multi-pool monitoring |
| **Setup complexity** | Minimal | Moderate |
| **Load distribution** | Per connection | Automatic balancing |
For workloads larger than 25 subscriptions, open multiple parallel POST streams.
***
## Quick Start
### 1. Choose Your Method
Stream Ethereum WETH price:
```bash theme={null}
curl -N "https://streaming.dexpaprika.com/sse/prices?method=token_price&chain=ethereum&address=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
```
Stream SOL and USDC prices:
```bash theme={null}
curl -X POST "https://streaming.dexpaprika.com/sse/prices" \
-H "Accept: text/event-stream" \
-H "Content-Type: application/json" \
-d '[
{"chain": "solana", "address": "So11111111111111111111111111111111111111112", "method": "token_price"},
{"chain": "ethereum", "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "method": "token_price"}
]'
```
### 2. Parse the Response
Each price update arrives as a JSON event. The default method `token_price` returns full field names:
```json theme={null}
{
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"chain": "ethereum",
"price": "2257.973351845954",
"timestamp": 1778846475,
"timestamp_price": 1778846474,
"token_price": 1778846474
}
```
The legacy `t_p` method emits a compact shape with single-letter keys (`a`, `c`, `p`, `t`, `t_p`). It is **deprecated** and kept only for backward compatibility.
### 3. Handle in Your Application
```javascript Browser theme={null}
const eventSource = new EventSource(url);
eventSource.addEventListener('token_price', (event) => {
const price = JSON.parse(event.data);
console.log(`${price.chain} ${price.address}: $${price.price}`);
});
// Optional: surface deprecation notices the server may push
eventSource.addEventListener('warning', (event) => {
const { message } = JSON.parse(event.data);
console.warn('[stream warning]', message);
});
eventSource.onerror = () => {
// Reconnect logic
eventSource.close();
setTimeout(connectStream, 1000);
};
```
```python Python theme={null}
import requests
import json
response = requests.get(url, stream=True)
# Buffer lines per SSE message (separated by blank lines), then dispatch.
# Both `event:`/`data:` orderings are valid SSE and the server has emitted
# either during rollout. A line-by-line parser that assumes one order will
# silently mis-dispatch. Always group first.
msg_lines = []
for line in response.iter_lines(decode_unicode=True):
if line:
msg_lines.append(line)
continue
event_type = "message"
data_str = None
for ml in msg_lines:
if ml.startswith("event:"):
event_type = ml.split(":", 1)[1].strip()
elif ml.startswith("data:"):
data_str = ml[5:].lstrip()
msg_lines = []
if event_type == "token_price" and data_str is not None:
data = json.loads(data_str)
print(f"{data['chain']} {data['address']}: ${data['price']}")
```
```typescript TypeScript theme={null}
interface PriceEvent {
address: string;
chain: string;
price: string;
timestamp: number;
timestamp_price: number;
}
const stream = new EventSource(url);
stream.addEventListener('token_price', (event: MessageEvent) => {
const price: PriceEvent = JSON.parse(event.data);
updatePrice(price);
});
```
***
## Architecture Overview
```mermaid theme={null}
graph LR
A[Your App] -->|SSE Connection| B[Streaming API]
B --> C[Load Balancer]
C --> D1[Server 1]
C --> D2[Server 2]
C --> D3[Server N]
D1 --> E[Price Feeds]
D2 --> E
D3 --> E
E --> F[Blockchain RPCs]
```
### Key Features
* **Automatic Load Balancing**: Requests distributed across multiple servers
* **Persistent Connections**: Single connection maintained for entire session
* **Efficient Updates**: Only sends data when prices actually change
* **Global Infrastructure**: Low-latency servers in multiple regions
***
## Limits & Quotas
Service limits enforced by the streaming gateway:
| Limit | Value | Notes |
| --------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Concurrent SSE streams per IP | **10** | Exceeding returns `429 Too Many Requests` with body `{"message": "ip stream limit exceeded"}` |
| Subscriptions per POST connection | **25** | `POST /sse/prices` rejects with `{"message":"too many assets, max 25 allowed"}`. `POST /sse/reserves` rejects with `{"message":"too many subscriptions"}` |
| Ping interval | **15 seconds** | A `ping` event is pushed periodically to keep the connection open |
| Credit cost | **1 credit per update delivered** | Connections are free. Only delivered events are charged, against 50,000 credits a month keyless, 300,000 with a free key, or 5M on [Pro](https://dexpaprika.com/api/pricing). See [plan your credit usage](/knowledge-base/credit-usage) |
### Event types you may receive
| Event | When | Payload |
| ---------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `token_price` | New price observation for a subscribed token | `{address, chain, price, timestamp, timestamp_price}` |
| `pool_reserves` | Pool reserves changed on the latest indexed block | `{chain, pool_id, block, previous_block, tokens[], total_reserve_usd, total_delta_usd}` |
| `token_reserves` | A token's aggregate reserves changed (across all its pools) | `{chain, token_id, reserve, delta, block, price_usd, reserve_usd, delta_usd, updated_at}` |
| `ping` | Every \~15s while idle | `{"time": }` |
| `warning` | Deprecation notices or other non-fatal signals | `{"message": "..."}` |
| `error` | Stream-terminating error (e.g. invalid asset) | `{"message": "..."}` |
| `t_p` | **Deprecated.** Legacy compact price shape (`{a, c, p, t, t_p}`). Use `token_price` instead. | |
Parsers should skip events whose `event:` field is not in the allow-list they care about. The set may grow over time (e.g. new `warning` event was added in May 2026), so an unrecognised event must never crash your handler.
***
## Best Practices
* Implement automatic reconnection with exponential backoff
* Handle both network errors and SSE error events
* Monitor connection health with heartbeat timeouts
* Validate all assets before streaming (invalid assets cancel entire stream)
* Parse both HTTP errors and SSE error events
* Log errors for debugging but don't expose sensitive data
* Use POST method for multiple assets (better than multiple GETs)
* Split large requests across multiple smaller streams
* Parse price strings carefully to maintain precision
* Implement proper logging and monitoring
* Set up alerts for connection drops
* Use connection pooling for multiple streams
* Consider WebSocket bridges for incompatible clients
***
## Supported Networks
Stream tokens from multiple blockchain networks. Use the [Networks API](/api-reference/networks/get-a-list-of-available-blockchain-networks) to get the full list of supported networks:
```bash theme={null}
curl https://api.dexpaprika.com/networks
```
Popular networks include:
* Ethereum (`ethereum`)
* Solana (`solana`)
* Binance Smart Chain (`bsc`)
* Arbitrum (`arbitrum`)
* Polygon (`polygon`)
* Base (`base`)
* Avalanche (`avalanche`)
The `chain` parameter in streaming requests must use the exact `id` value from the [Networks endpoint](/api-reference/networks/get-a-list-of-available-blockchain-networks).
***
## Finding Token Addresses
Before streaming prices, you need the correct token address for your chosen network. Use these REST API endpoints:
### Search for Tokens
Use the [Search API](/api-reference/search/search-for-tokens-pools-and-dexes) to find tokens by name or symbol:
```bash theme={null}
curl "https://api.dexpaprika.com/search?query=USDC"
```
### Find a Token's Pools
List pools for a specific token using [pool search](/tutorials/pool-filtering) with the `token_address` filter (per-network search only; the cross-network `/pools/search` ignores it):
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?token_address=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
```
### Validate Before Streaming
Use the [Token Prices API](/api-reference/tokens/get-batched-token-prices-on-a-network) to verify your token exists before streaming:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/multi/prices?tokens=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
```
Always verify the token address matches the network you're streaming from. The same token may have different addresses on different chains.
The streaming API will return a `400 Asset not found` error if the token doesn't exist on the specified network. Validate tokens using the REST API first to avoid stream failures.
***
## Next Steps
The deep dive on `/sse/reserves`: what gets pushed when reserves move, the two subscription methods, annotated wire example, use cases for LPs, MEV builders, and analytics teams.
Build your first streaming application in 5 minutes. Covers both prices and reserves.
Interactive streaming demo. Pick a chain, pick tokens, watch the SSE events flow in real time.
Build streaming components for React and Next.js applications.
***
## Get Support
Join our Discord for real-time support and discussions.
Contact our team for technical assistance.
# Real-Time Pool Reserves Streaming
Source: https://docs.dexpaprika.com/streaming/reserves-streaming
Subscribe to block-by-block, USD-denominated liquidity changes for any DEX pool or any token, pushed over Server-Sent Events.
DexPaprika streams parsed pool reserves over Server-Sent Events. Every time a swap or liquidity change lands in a pool you subscribed to, you receive a `pool_reserves` or `token_reserves` event (named after your subscription method) containing the current reserve and delta for each token in the pool, the current USD price, the USD value of the change, and the block number. No polling. No log decoding. No competitor offers this as a push feed.
**DexPaprika streams parsed pool reserves as push events.** For every block in which a subscribed pool's reserves changed, for every token in it: current reserve, delta since the previous block, current USD price, USD value of the change. No polling. No log decoding. No DEX-specific contract integrations. Just connect and consume.
## What you get
Every time a swap, add-liquidity, or remove-liquidity transaction lands in a pool you subscribed to, DexPaprika emits a `pool_reserves` event (or `token_reserves` for token subscriptions) containing:
* The **chain** and **pool** the change happened in
* The **block number** the change was observed in
* For **each token** in the pool:
* Current reserve (raw on-chain integer)
* Delta since the previous block (raw on-chain integer)
* Current USD price
* Current reserve value in USD
* USD value of this block's delta
* **Total reserve value of the pool in USD**
* **Total USD delta** across all tokens this block
That last value, `total_delta_usd`, tells you in one number how much liquidity moved through the pool this block. Positive means net inflow, negative means net outflow. No reduction, no aggregation, no waiting.
***
## Why this is unique
Real-time pool reserves are easy to describe and hard to ship. Every other approach in the market makes you do most of the work.
### How competitors handle this today
Subgraphs index DEX events into queryable schemas, and are most commonly consumed as **polling GraphQL queries**. Push subscriptions exist in some gateway configurations but aren't the default consumption pattern most teams build against. Latency in the typical setup is bounded below by your poll interval. For 1,000 pools and 1-second polling that is 60,000 requests per minute, every minute, forever.
These providers stream **raw blockchain events**. You receive a `Sync(uint112, uint112)` log for Uniswap V2, a `Swap(...)` log with internal balance math for Uniswap V3, an entirely different shape for Curve, Balancer, Solana DEXes. To turn that into "pool X now has Y USDC and Z WETH" you write a contract-by-contract decoder, maintain it across forks, handle reorgs, and reconcile reserve state from emitted deltas. That is tens of thousands of lines of code per DEX integration before you ever see a single USD-denominated reserve value.
A handful of providers offer token-price websockets. They push "USDC is $1.00" in real time. To our knowledge, they don't surface pool-level reserve dynamics as push events: you can see that USDC's price moved, but not that pool X just shed $4M in WETH liquidity this block.
Aggregator APIs are quote-driven, not stream-driven. You request a swap quote, you get pool depth as a side effect. You cannot subscribe.
These are query-and-dashboard platforms. Their freshness is measured in minutes. Real-time is not the product.
### How DexPaprika handles it
We do the contract decoding, the cross-DEX reserve reconciliation, the price attachment, and the USD denomination on our side. You get a single SSE stream. The events look the same whether the underlying pool is Uniswap V2, V3, V4, Curve, Balancer, Aerodrome, Raydium, or Orca.
| Provider | Reserves push events | Block-level granularity | USD-denominated deltas | Multi-DEX abstraction | Zero log decoding |
| ---------------------------- | ----------------------------------- | ----------------------- | ---------------------- | --------------------- | ----------------- |
| **DexPaprika Streaming** | **Yes** | **Yes** | **Yes** | **Yes** | **Yes** |
| Subgraphs (The Graph) | No (poll) | Yes | Sometimes | Per-subgraph | No |
| Alchemy / Infura / QuickNode | Raw logs only | Yes | No | No | No |
| Birdeye | Token prices only | Token-level | No | N/A | N/A |
| Dune / Defillama | No (dashboards) | Minute-scale | Yes | Yes | Yes |
| GeckoTerminal | No | N/A | N/A | N/A | N/A |
| Bitquery | GraphQL subscriptions on raw events | Yes | Limited | Limited | Partial |
DexPaprika is the only row that says yes across all five.
***
## The two subscription methods
The `/sse/reserves` endpoint accepts two `method` values. Pick the one that matches what you are actually trying to monitor.
Subscribe to **one specific pool**. You receive an event every time a swap or liquidity change lands in that pool's smart contract. Use this when you care about a particular pair on a particular DEX (a Uniswap V3 USDC/WETH pool, a Curve 3pool position, a Raydium AMM you provide liquidity to).
Subscribe to **one specific token**. You receive an event every time **any pool containing that token** sees a reserve change. Use this when you care about a token's entire on-chain ecosystem at once: total liquidity across all pools, where volume is concentrated, where it's draining.
### When to pick which
**Pick `pool_reserves` if you are**: an LP watching your specific position, an arbitrage bot focused on a single venue, a slippage estimator for a particular trade route, a Uniswap V4 hook integrator.
**Pick `token_reserves` if you are**: a treasury monitoring a stablecoin you issue, a token issuer watching liquidity migration across venues, an analytics platform aggregating TVL per token, a risk system watching for sudden drains anywhere a token trades.
You can run both subscriptions on the same connection. A multi-asset POST stream can mix `pool_reserves` and `token_reserves` entries freely.
***
## What an event looks like
Live capture from the production stream:
```json theme={null}
{
"chain": "ethereum",
"pool_id": "0xe0554a476a092703abdb3ef35c80e0d76d32939f",
"block": "25098911",
"tokens": [
{
"token_id": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"reserve": "1897184450989",
"delta": "-4915987385",
"price_usd": 1.0000070290769338,
"reserve_usd": 1897197.78,
"delta_usd": -4916.02
},
{
"token_id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"reserve": "1575557282142409643389",
"delta": "2180365462384895407",
"price_usd": 2255.21,
"reserve_usd": 3553216.99,
"delta_usd": 4917.19
}
],
"total_reserve_usd": 5450414.77,
"total_delta_usd": 1.16
}
event: pool_reserves
```
Reading this top to bottom:
* The change happened **on Ethereum, in pool `0xe055...939f`** (Uniswap V3 USDC/WETH), at **block 25098911**. Note that `block`, `reserve`, and `delta` arrive as JSON strings (precision-safe encoding for values that exceed `Number.MAX_SAFE_INTEGER`).
* **Token 0 is USDC.** Its reserve dropped by **4,915,987,385 raw units** this block. USDC has 6 decimals, so that is **−\$4,916**.
* **Token 1 is WETH.** Its reserve rose by **2,180,365,462,384,895,407 raw units**. WETH has 18 decimals, so that is **+2.18 WETH**, which at current $2,255 ETH equals **+$4,917\*\*.
* **Net dollar delta for the pool this block: \~\$1.16.** Which is what `total_delta_usd` says.
What you just witnessed is a swap: someone bought \$4,916 worth of WETH with USDC. You can see it block-by-block, without reading a single contract event log.
***
## Use cases by audience
The same feature, three different reasons to care.
### For DeFi power users
If you provide liquidity, trade, or hold positions on DEXes, reserves streaming changes what you can know about your own money.
Watch your LP position's IL accrue block by block. Every `pool_reserves` event for your pool gives you the new constant-product or concentrated-liquidity state. Plug it into an IL calculator and your dashboard tells you what you are losing right now, not what you lost an hour ago.
Get the exact reserves of the pool you are about to trade through, as of the current block. Compute expected slippage against the actual current state. Decide whether to route, split, or wait.
Watch a token's entire pool ecosystem (`method=token_reserves`). When a major LP yanks liquidity from venue A and adds to venue B, you see both events within seconds.
Sudden, large, one-sided reserve drains on a small pool. Exactly the signal you want before everyone else notices. Wire `total_delta_usd < -threshold` into a webhook and you have it.
### For quant, MEV, and arbitrage builders
You need fresh, deterministic, low-latency pool state across many venues. Polling-based APIs do not work for you. Raw log streams force you to be your own DEX integration team.
Every event carries the `block` it was observed in. Two events with the same `block` happened in the same block. Two events with different `block` values are strictly ordered. You can reconstruct per-block pool state without ambiguity.
Uniswap V2, V3, V4, Curve, Balancer, Aerodrome, Raydium, Orca, PancakeSwap, SushiSwap. They all emit the same `pool_reserves` shape. One parser, one schema, one mental model. The contract-by-contract decoding lives on our side.
`price_usd` is attached to every token's reserve at observation time. `delta_usd` tells you the dollar value of this block's change. You do not maintain your own price feed to compute it.
The streaming infrastructure is built for high concurrency. Production load testing has demonstrated comfortable headroom under hundreds of simultaneous connections, with linear throughput scaling under realistic workloads.
### For analytics and dashboard builders
You build the products people open in the morning to see what is happening on-chain. Your users expect numbers that are accurate to the block, not to the minute.
Sum `total_reserve_usd` across all pools you track. The number is current to the block. No background ETL job. No "data is fresh as of 2 minutes ago" disclaimer.
Aggregate `abs(total_delta_usd)` per pool, per DEX, per chain, over your time window of choice. You get DEX volume as a live stream, not a delayed batch.
Maintain a per-pool reserves snapshot in memory, updated by every event. Render heatmaps that update live as liquidity moves across pools, chains, and DEXes.
When a bridge contract sits as a counterparty in a pool, its reserve changes show you cross-chain flows the moment they settle on the destination chain. No "is the bridge healthy" guessing.
***
## Get started
Three runnable examples. Each connects to the live production stream, with no API key needed to start. Before you leave one running, size the cost with [plan your credit usage](/knowledge-base/credit-usage).
### A single pool with cURL
Subscribe to a Uniswap V3 USDC/WETH pool on Ethereum.
```bash theme={null}
curl -N "https://streaming.dexpaprika.com/sse/reserves?method=pool_reserves&chain=ethereum&address=0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640"
```
You will see events arrive each time someone trades through this pool:
```
data: {"chain":"ethereum","pool_id":"0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640","block":"25098912","tokens":[...],"total_reserve_usd":5042113.41,"total_delta_usd":-128.44}
event: pool_reserves
```
Use `Ctrl-C` to disconnect.
### A whole token's ecosystem with Python
Subscribe to every pool containing USDC across Ethereum. This will emit a lot of events.
```python theme={null}
import json
import requests
url = "https://streaming.dexpaprika.com/sse/reserves"
params = {
"method": "token_reserves",
"chain": "ethereum",
"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", # USDC
}
with requests.get(url, params=params, stream=True) as response:
response.raise_for_status()
# Buffer one SSE message at a time, then dispatch. Both `event:`/`data:`
# orderings are valid SSE and the server has emitted either during rollout,
# so grouping is mandatory: a line-by-line parser that assumes one order
# will silently mis-dispatch on the other.
msg_lines = []
for raw in response.iter_lines(decode_unicode=True):
if raw:
msg_lines.append(raw)
continue
event_type = "message"
data_str = None
for ml in msg_lines:
if ml.startswith("event:"):
event_type = ml.split(":", 1)[1].strip()
elif ml.startswith("data:"):
data_str = ml[5:].lstrip()
msg_lines = []
if event_type not in ("pool_reserves", "token_reserves") or data_str is None:
continue
event = json.loads(data_str)
pool = event["pool_id"]
block = event["block"]
net = event["total_delta_usd"]
print(f"block {block} pool {pool[:10]}... net {net:+,.2f} USD")
```
Sample output:
```
block 25098905 pool 0x168e9b3c... net +2.24 USD
block 25098905 pool 0x0f91b8bb... net +0.14 USD
block 25098905 pool 0xff4ce5aa... net +0.04 USD
block 25098905 pool 0xe0554a47... net -0.03 USD
...
```
Each line is a separate pool that contains USDC, reporting its block-level reserve delta.
### Multiple assets at once with JavaScript
A POST stream that mixes one specific pool and one whole token. Single connection, both feeds.
```javascript theme={null}
async function subscribe() {
const response = await fetch("https://streaming.dexpaprika.com/sse/reserves", {
method: "POST",
headers: {
"Accept": "text/event-stream",
"Content-Type": "application/json",
},
body: JSON.stringify([
{
chain: "ethereum",
address: "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
method: "pool_reserves",
},
{
chain: "ethereum",
address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
method: "token_reserves",
},
]),
});
const decoder = new TextDecoder();
let buffer = "";
for await (const chunk of response.body) {
buffer += decoder.decode(chunk, { stream: true });
// SSE messages are separated by a blank line.
const messages = buffer.split("\n\n");
buffer = messages.pop() ?? "";
for (const message of messages) {
const lines = message.split("\n");
const eventLine = lines.find((l) => l.startsWith("event:"));
const dataLine = lines.find((l) => l.startsWith("data:"));
if (!dataLine) continue;
// These event types may arrive: `pool_reserves` / `token_reserves`, `ping`, `warning`, `error`.
// Filter for the one we care about; unrecognised events should be no-ops.
const eventType = eventLine ? eventLine.slice(6).trim() : "message";
if (eventType !== "pool_reserves" && eventType !== "token_reserves") continue;
const payload = JSON.parse(dataLine.slice(5).trim());
handleReserveUpdate(payload);
}
}
}
function handleReserveUpdate(event) {
console.log(`pool ${event.pool_id} block ${event.block} Δ ${event.total_delta_usd.toFixed(2)} USD`);
}
subscribe();
```
Two notes on the JavaScript example:
* The browser `EventSource` API does **not** support POST requests, so multi-asset subscriptions on the browser require `fetch` + `ReadableStream` like above. Single-asset GET subscriptions work with `EventSource` directly.
* The `total_delta_usd`, `delta_usd`, `reserve_usd`, and `price_usd` fields are regular JavaScript numbers, safe for floating-point math. The raw `reserve`, `delta`, `block`, and `previous_block` fields arrive as JSON strings: the server encodes them that way because they routinely exceed `Number.MAX_SAFE_INTEGER`. Use `BigInt(reserve)` for exact arithmetic, or `Number(block)` if you just need to compare block heights and trust the value fits in a 53-bit float.
***
## Limits to know about
| Limit | Value | Behaviour when exceeded |
| -------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Pools/tokens per POST connection | **25** | `POST /sse/reserves` rejects the request with `{"message": "too many subscriptions"}` |
| Concurrent SSE streams per IP | **10** | The 11th connection returns `429 Too Many Requests` with `{"message": "ip stream limit exceeded"}` |
| Ping interval | **15 s** | A `ping` event arrives if no reserve activity has been pushed for that long. Use it as a connection-liveness signal |
| Credit cost | **1 per event delivered** | The caps above are the same on every plan; the monthly allowance is not. 50,000 credits keyless, 300,000 with a free key, 5M on [Pro](https://dexpaprika.com/api/pricing) |
The server may also push a `warning` event in-band (for example, when you connect to a deprecated path). Treat unknown event types as no-ops so new server-side additions never crash your handler.
***
## What's next
Schema, parameters, error codes for the GET and POST `/sse/reserves` endpoints.
DexPaprika also streams token prices over the same SSE transport. Use both together for full pool-state coverage.
A working frontend that connects to the streaming API. Source-available, deployable, a good starting reference.
The team is in Discord. Bring use cases, edge cases, and feature requests.
# Showcase streams: the keyless token list
Source: https://docs.dexpaprika.com/streaming/showcase-streams
The one flagship asset per chain you can stream without an API key, with its contract address and a live demo for every one of the 36 chains.
Price streaming works without an API key, on a fixed list: one flagship asset per chain, 36 in total. These are the showcase streams. They exist so you can see real pushed prices before you sign up for anything.
Streaming is three separate products, and only the first is open keyless:
| Endpoint | What it pushes | Without a key |
| ------------------- | -------------------------------------- | ---------------------------- |
| `/sse/prices` | USD price, pushed when a swap moves it | The 36 showcase assets below |
| `/sse/transactions` | Individual swaps on a pool or token | Needs a free key |
| `/sse/reserves` | Pool reserves and their deltas | Needs a free key |
A free API key removes both restrictions. With a key you can stream any token we index on all three endpoints, up to 10 concurrent connections per IP. Nothing else changes: the transport, the event shape and the delivery rate are the same either way. See [pricing](https://dexpaprika.com/api/pricing) for how streaming is metered.
## The list
Every row links to a live stream for that chain, running in the browser with no key and no signup. Verified against the streaming gateway on 2026-08-06.
| Network | Asset | Contract address | Live stream |
| ------------------- | --------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| Aptos | `APT` | `0xa` | [Watch APT on Aptos](https://dexpaprika.com/streaming/aptos) |
| Arbitrum | `WETH` | `0x82af49447d8a07e3bd95bd0d56f35241523fbab1` | [Watch WETH on Arbitrum](https://dexpaprika.com/streaming/arbitrum) |
| Avalanche | `WAVAX` | `0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7` | [Watch WAVAX on Avalanche](https://dexpaprika.com/streaming/avalanche) |
| Base | `WETH` | `0x4200000000000000000000000000000000000006` | [Watch WETH on Base](https://dexpaprika.com/streaming/base) |
| Berachain | `WBERA` | `0x6969696969696969696969696969696969696969` | [Watch WBERA on Berachain](https://dexpaprika.com/streaming/berachain) |
| Binance Smart Chain | `WBNB` | `0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c` | [Watch WBNB on Binance Smart Chain](https://dexpaprika.com/streaming/bsc) |
| Blast | `WETH` | `0x4300000000000000000000000000000000000004` | [Watch WETH on Blast](https://dexpaprika.com/streaming/blast) |
| BOB Network | `WETH` | `0x4200000000000000000000000000000000000006` | [Watch WETH on BOB Network](https://dexpaprika.com/streaming/bob_network) |
| Botanix | `pBTC` | `0x0d2437f93fed6ea64ef01ccde385fb1263910c56` | [Watch pBTC on Botanix](https://dexpaprika.com/streaming/botanix) |
| Celo | `CELO` | `0x471ece3750da237f93b8e339c536989b8978a438` | [Watch CELO on Celo](https://dexpaprika.com/streaming/celo) |
| Cronos | `WCRO` | `0x5c7f8a570d578ed84e63fdfa7b1ee72deae1ae23` | [Watch WCRO on Cronos](https://dexpaprika.com/streaming/cronos) |
| Ethereum | `WETH` | `0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2` | [Watch WETH on Ethereum](https://dexpaprika.com/streaming/ethereum) |
| Fantom | `WFTM` | `0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83` | [Watch WFTM on Fantom](https://dexpaprika.com/streaming/fantom) |
| Flow EVM | `WFLOW` | `0xd3bf53dac106a0290b0483ecbc89d40fcc961f3e` | [Watch WFLOW on Flow EVM](https://dexpaprika.com/streaming/flow_evm) |
| HyperEVM | `WHYPE` | `0x5555555555555555555555555555555555555555` | [Watch WHYPE on HyperEVM](https://dexpaprika.com/streaming/hyperevm) |
| Katana | `vbETH` | `0xee7d8bcfb72bc1880d0cf19822eb0a2e6577ab62` | [Watch vbETH on Katana](https://dexpaprika.com/streaming/katana) |
| Linea | `WETH` | `0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f` | [Watch WETH on Linea](https://dexpaprika.com/streaming/linea) |
| Mantle | `WMNT` | `0x78c1b0c915c4faa5fffa6cabf0219da63d7f4cb8` | [Watch WMNT on Mantle](https://dexpaprika.com/streaming/mantle) |
| MegaETH | `WETH` | `0x4200000000000000000000000000000000000006` | [Watch WETH on MegaETH](https://dexpaprika.com/streaming/megaeth) |
| Monad | `WMON` | `0x3bd359c1119da7da1d913d1c4d2b7c461115433a` | [Watch WMON on Monad](https://dexpaprika.com/streaming/monad) |
| Optimism | `WETH` | `0x4200000000000000000000000000000000000006` | [Watch WETH on Optimism](https://dexpaprika.com/streaming/optimism) |
| Plasma | `WXPL` | `0x6100e367285b01f48d07953803a2d8dca5d19873` | [Watch WXPL on Plasma](https://dexpaprika.com/streaming/plasma) |
| Polygon | `WPOL` | `0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270` | [Watch WPOL on Polygon](https://dexpaprika.com/streaming/polygon) |
| Robinhood Chain | `WETH` | `0x0bd7d308f8e1639fab988df18a8011f41eacad73` | [Watch WETH on Robinhood Chain](https://dexpaprika.com/streaming/robinhood) |
| Ronin | `WRON` | `0xe514d9deb7966c8be0ca922de8a064264ea6bcd4` | [Watch WRON on Ronin](https://dexpaprika.com/streaming/ronin) |
| Scroll | `WETH` | `0x5300000000000000000000000000000000000004` | [Watch WETH on Scroll](https://dexpaprika.com/streaming/scroll) |
| Sei | `WSEI` | `0xe30fedd158a2e3b13e9badaeabafc5516e95e8c7` | [Watch WSEI on Sei](https://dexpaprika.com/streaming/sei) |
| Solana | `SOL` | `So11111111111111111111111111111111111111112` | [Watch SOL on Solana](https://dexpaprika.com/streaming/solana) |
| Sonic | `wS` | `0x039e2fb66102314ce7b64ce5ce3e5183bc94ad38` | [Watch wS on Sonic](https://dexpaprika.com/streaming/sonic) |
| Sui | `SUI` | `0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI` | [Watch SUI on Sui](https://dexpaprika.com/streaming/sui) |
| Tempo | `pathUSD` | `0x20c0000000000000000000000000000000000000` | [Watch pathUSD on Tempo](https://dexpaprika.com/streaming/tempo) |
| Ton | `TON` | `EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c` | [Watch TON on Ton](https://dexpaprika.com/streaming/ton) |
| Tron | `WTRX` | `TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR` | [Watch WTRX on Tron](https://dexpaprika.com/streaming/tron) |
| Unichain | `WETH` | `0x4200000000000000000000000000000000000006` | [Watch WETH on Unichain](https://dexpaprika.com/streaming/unichain) |
| x Layer | `WOKB` | `0xe538905cf8410324e03a5a23c1c177a474d59b2b` | [Watch WOKB on x Layer](https://dexpaprika.com/streaming/x_layer) |
| ZkSync | `WETH` | `0x5aea5775959fbc2557cc8789bc1bf90a239d9a91` | [Watch WETH on ZkSync](https://dexpaprika.com/streaming/zksync) |
## Subscribing to one
Take the chain id and the contract address from the row you want. No key, no headers:
```bash theme={null}
curl -N "https://streaming.dexpaprika.com/sse/prices?method=token_price&chain=solana&address=So11111111111111111111111111111111111111112"
```
Each `token_price` frame carries the asset, the chain and the current USD price. Updates are pushed when a swap moves the price, not on a fixed interval, so a heavily traded asset delivers tens of frames a minute while a quiet one can sit still for minutes. That is the market being quiet, not the connection being broken.
## Requesting an asset that is not on the list
Without a key, a subscription is accepted only for the assets above. The POST path rejects the whole subscribe on the first asset outside the list rather than returning a partial result, so a batch that mixes listed and unlisted assets fails as a unit. Register a free key and the restriction disappears.
A free API key takes you off this list: any token we index, up to 10 concurrent streams per IP. No card.
# Stream real-time prices for an asset
Source: https://docs.dexpaprika.com/streaming/stream-real-time-data-for-a-single-asset
get /sse/prices
Establishes an SSE stream for a single token's price using query parameters.
**Emitted events:** `token_price` · `ping` · `warning` · `error`
# Stream real-time reserve updates for a single pool or token
Source: https://docs.dexpaprika.com/streaming/stream-real-time-reserves-for-a-single-asset
get /sse/reserves
Establishes an SSE stream for reserve updates on a specific pool or token using query string parameters.
**Supported Channel Events:** - `pool_reserves`: Per-block reserve update for a pool. Schema matches `PoolReservesResponse`. - `token_reserves`: Aggregated reserve update across all pools for a token. Schema matches `TokenReservesResponse`. - `ping`: Infrastructure keep-alive payload (`PingEvent`). - `warning`: System warning notifications (`WarningEvent`). - `error`: Stream-level operational exceptions (`ErrorEvent`).
# Stream real-time prices for multiple assets
Source: https://docs.dexpaprika.com/streaming/subscribe-to-multiple-assets-streaming
post /sse/prices
Establishes a multiplexed SSE stream for up to 25 token price subscriptions in a single connection.
**Emitted events:** `token_price` · `ping` · `warning` · `error`
# Stream real-time reserve updates for multiple pools or tokens
Source: https://docs.dexpaprika.com/streaming/subscribe-to-multiple-reserves-streaming
post /sse/reserves
Establishes a multiplexed SSE stream for reserve updates across multiple pools or tokens within a single connection.
**Stream Constraints:** Maximum payload array length is **25 subscriptions**.
**Supported Channel Events:** - `pool_reserves`: Per-block reserve update for a pool. Schema matches `PoolReservesResponse`. - `token_reserves`: Aggregated reserve update for a token. Schema matches `TokenReservesResponse`. - `ping`: Infrastructure keep-alive payload (`PingEvent`). - `error`: Stream-level operational exceptions (`ErrorEvent`).
# Quick Start: Your First Stream
Source: https://docs.dexpaprika.com/streaming/tutorials/quick-start
Stream live token prices and pool reserves in under 10 minutes. Two feeds, one SSE transport, no API key needed to start.
**See it in action:** Check out our [Live Streaming Dashboard](https://dexpaprika.com/streaming/solana) to see real-time updates flow across multiple chains before you build your own.
## Tutorial overview
In this tutorial, you'll learn how to:
* Connect to the DexPaprika streaming API
* Receive real-time **token price** updates
* Receive real-time **pool reserve** updates (the DexPaprika differentiator)
* Handle connection errors gracefully
* Build a simple live display
**Time to complete**: 5 to 10 minutes
**Prerequisites**: Basic JavaScript or Python knowledge
***
## Step 1: Choose Your Asset
For this tutorial, we'll stream the price of Ethereum (WETH).
### Finding Token Addresses
Use the REST API to find token addresses:
1. **Search by name or symbol** using the [Search Endpoint](/api-reference/search/search-for-tokens-pools-and-dexes):
```bash theme={null}
curl "https://api.dexpaprika.com/search?query=WETH"
```
2. **Get network list** using the [Networks Endpoint](/api-reference/networks/get-a-list-of-available-blockchain-networks):
```bash theme={null}
curl "https://api.dexpaprika.com/networks"
```
### Common Token Addresses
Or use these verified addresses for popular tokens:
```
Chain: ethereum
WETH: 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
USDC: 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
```
```
Chain: solana
SOL: So11111111111111111111111111111111111111112
USDC: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
```
```
Chain: bsc
BNB: 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c
USDT: 0x55d398326f99059ff775485246999027b3197955
```
***
## Step 2: Test with cURL
First, verify the stream works with a simple cURL command:
```bash theme={null}
curl -N "https://streaming.dexpaprika.com/sse/prices?method=token_price&chain=ethereum&address=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&limit=3"
```
Live capture from production, three events back-to-back:
```
data: {"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","chain":"ethereum","price":"2255.213769366838","timestamp":1778847591,"timestamp_price":1778847590,"token_price":1778847590}
event: token_price
data: {"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","chain":"ethereum","price":"2255.5995720362835","timestamp":1778847592,"timestamp_price":1778847592,"token_price":1778847592}
event: token_price
data: {"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","chain":"ethereum","price":"2255.5995720362835","timestamp":1778847593,"timestamp_price":1778847593,"token_price":1778847593}
event: token_price
```
The `limit=3` parameter closes the stream after three events, perfect for smoke tests. Drop it for a continuous feed.
Both orderings of `event:` and `data:` lines within a single SSE message are valid per the [SSE specification](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events), and the server has used both during rollout. Only the blank-line boundary between messages is significant. `EventSource` handles either order natively. Custom parsers should buffer one message at a time and dispatch on the parsed event type, not on field order.
***
## Step 3: JavaScript Implementation
Here's how to connect to the streaming API using JavaScript:
```javascript theme={null}
// Connect to streaming API
const url = new URL('https://streaming.dexpaprika.com/sse/prices');
url.searchParams.set('method', 'token_price');
url.searchParams.set('chain', 'ethereum');
url.searchParams.set('address', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2');
const eventSource = new EventSource(url.toString());
// Handle price updates
eventSource.addEventListener('token_price', (event) => {
const data = JSON.parse(event.data);
const price = parseFloat(data.price);
console.log(`WETH Price: $${price.toFixed(2)}`);
});
// Surface server-side notices (deprecations, etc.)
eventSource.addEventListener('warning', (event) => {
const { message } = JSON.parse(event.data);
console.warn('[stream warning]', message);
});
// Handle errors with reconnection
eventSource.onerror = () => {
console.log('Connection lost, reconnecting...');
eventSource.close();
// Implement reconnection logic here
};
```
**Live example:** See this code in action in our [Interactive Streaming Dashboard](https://dexpaprika.com/streaming/solana) - watch 6 tokens stream live across Ethereum, Solana, and BSC with real-time updates, connection status, and latency monitoring.
***
## Step 4: Customize Your Stream
### Stream Different Tokens
Modify the URL parameters to stream any token:
```javascript theme={null}
// Bitcoin on Ethereum
url.searchParams.set('chain', 'ethereum');
url.searchParams.set('address', '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599');
// SOL on Solana
url.searchParams.set('chain', 'solana');
url.searchParams.set('address', 'So11111111111111111111111111111111111111112');
// BNB on BSC
url.searchParams.set('chain', 'bsc');
url.searchParams.set('address', '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c');
```
### Add Multiple Assets
To stream multiple assets, switch to the POST method:
```javascript theme={null}
async function streamMultipleAssets() {
const assets = [
{ chain: 'ethereum', address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', method: 'token_price' },
{ chain: 'solana', address: 'So11111111111111111111111111111111111111112', method: 'token_price' }
];
const response = await fetch('https://streaming.dexpaprika.com/sse/prices', {
method: 'POST',
headers: {
'Accept': 'text/event-stream',
'Content-Type': 'application/json'
},
body: JSON.stringify(assets)
});
const decoder = new TextDecoder();
let buffer = '';
for await (const chunk of response.body) {
buffer += decoder.decode(chunk, { stream: true });
const messages = buffer.split('\n\n');
buffer = messages.pop() ?? '';
for (const message of messages) {
const lines = message.split('\n');
const eventLine = lines.find(l => l.startsWith('event:'));
const dataLine = lines.find(l => l.startsWith('data:'));
if (!dataLine) continue;
const eventType = eventLine ? eventLine.slice(6).trim() : 'message';
if (eventType !== 'token_price') continue; // skip ping/warning/etc
const update = JSON.parse(dataLine.slice(5).trim());
console.log(`${update.chain} ${update.address}: $${parseFloat(update.price).toFixed(4)}`);
}
}
}
```
Pass up to 25 entries per connection. `POST /sse/prices` rejects larger arrays with `{"message":"too many assets, max 25 allowed"}` (the sibling `POST /sse/reserves` endpoint uses different wording: `"too many subscriptions"`). Open additional connections in parallel if you need more, up to the **10 concurrent SSE streams per IP** limit.
***
## Understanding the Code
### Key Components
The `EventSource` API is the browser's built-in way to handle SSE:
```javascript theme={null}
const eventSource = new EventSource(url);
eventSource.addEventListener('token_price', handler);
```
It automatically handles:
* Persistent connections
* Automatic reconnection
* Event parsing
The `onerror` callback handles disconnections:
```javascript theme={null}
eventSource.onerror = () => {
// Implement exponential backoff
const delay = baseDelay * Math.pow(2, attempts);
setTimeout(reconnect, delay);
};
```
Each `token_price` event contains:
```javascript theme={null}
{
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"chain": "ethereum",
"price": "2255.5995720362835", // USD, string to preserve precision
"timestamp": 1778847592, // server send time (unix)
"timestamp_price": 1778847592, // price observation time
"token_price": 1778847592 // alias of timestamp_price
}
```
The legacy `t_p` method emits a compact `{a, c, p, t, t_p}` shape and is **deprecated**.
Beyond `token_price`, `pool_reserves`, and `token_reserves`, the server may push:
* `ping` every \~15s (keep-alive). Payload: `{"time": }`.
* `warning` for non-fatal notices, including deprecation messages. Payload: `{"message": "..."}`.
* `error` for stream-terminating problems (invalid asset, etc.).
Always filter on the `event:` line. Treat unknown event names as no-ops so future server-side additions cannot break your handler.
***
## Now stream pool reserves
Token prices are useful. Block-level pool reserves are the killer feature. Instead of `/sse/prices`, point at `/sse/reserves` and pick one of two methods:
* `method=pool_reserves`: subscribe to a specific pool. You receive events when that pool's reserves change.
* `method=token_reserves`: subscribe to a specific token. You receive events for every pool the token sits in.
### Try it with cURL
Subscribe to a Uniswap V3 USDC/WETH pool on Ethereum:
```bash theme={null}
curl -N "https://streaming.dexpaprika.com/sse/reserves?method=pool_reserves&chain=ethereum&address=0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640"
```
Each `pool_reserves` event tells you the pool, the block it was observed in, both tokens' raw reserves, the block's delta, current USD prices, and the dollar value of the change. A live capture from production (block 25,100,507):
```
data: {"chain":"ethereum","pool_id":"0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640","block":"25100507","tokens":[{"token_id":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","reserve":"70835095690418","delta":"12018780248","price_usd":1.0000312133558542,"reserve_usd":70837306.69,"delta_usd":12019.15},{"token_id":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","reserve":"14194377792871748830512","delta":"-5325593173019115725","price_usd":2255.213769366838,"reserve_usd":32011356.24,"delta_usd":-12010.35}],"total_reserve_usd":102848662.93,"total_delta_usd":8.80}
event: pool_reserves
```
That is a single swap: \~$12,019 of USDC came in, ~$12,010 of WETH went out, net dollar delta to the pool was +\$8.80 (the trader paid the trading fee). The whole picture, in one event, no log decoding required.
The `reserve`, `delta`, `block`, and `previous_block` fields all arrive as JSON strings (note the quotes in the example above). The server encodes them that way because they routinely exceed `Number.MAX_SAFE_INTEGER`: the WETH `delta` above is a 19-digit integer. Use `BigInt(reserve)` for exact arithmetic, or `Number(block)` when you just want a quick block-height comparison. The pre-computed USD fields (`reserve_usd`, `delta_usd`, `total_reserve_usd`, `total_delta_usd`) and `price_usd` are regular JSON numbers, safe for floating-point math.
### Same thing in JavaScript
```javascript theme={null}
async function subscribeReserves() {
const url = new URL("https://streaming.dexpaprika.com/sse/reserves");
url.searchParams.set("method", "pool_reserves");
url.searchParams.set("chain", "ethereum");
url.searchParams.set("address", "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640");
const response = await fetch(url.toString(), { headers: { Accept: "text/event-stream" } });
const decoder = new TextDecoder();
let buffer = "";
// Async-iterate the response body. This pattern handles gzip/transfer encodings
// transparently in both Node and modern browsers, and never blocks on partial
// chunks the way `getReader().read()` can.
for await (const chunk of response.body) {
buffer += decoder.decode(chunk, { stream: true });
// SSE messages are separated by a blank line.
const messages = buffer.split("\n\n");
buffer = messages.pop() ?? "";
for (const message of messages) {
const lines = message.split("\n");
const eventLine = lines.find((l) => l.startsWith("event:"));
const dataLine = lines.find((l) => l.startsWith("data:"));
if (!dataLine) continue;
// The stream emits: `pool_reserves` (or `token_reserves`), `ping`, `warning`, `error`.
// Filter for the one we care about so keep-alives and notices
// never reach the reserve handler.
const eventType = eventLine ? eventLine.slice(6).trim() : "message";
if (eventType !== "pool_reserves" && eventType !== "token_reserves") continue;
const update = JSON.parse(dataLine.slice(5).trim());
console.log(`block ${update.block} pool delta ${update.total_delta_usd.toFixed(2)} USD`);
}
}
}
subscribeReserves();
```
Want the deep dive on what reserves streaming unlocks (impermanent loss tracking, MEV detection, real-time TVL, slippage estimation)? See the [Reserves Streaming page](/streaming/reserves-streaming) for the annotated wire example and use cases broken out by audience.
***
## Implementation Notes
### Browser Support
* EventSource API works in all modern browsers
* For direct browser connections, you'll need to handle CORS (use a backend proxy)
* No issues when streaming from server-side (Node.js, Python, etc.)
### Best Practices
* Implement exponential backoff for reconnection
* Handle both connection errors and SSE error events
* Parse prices as floats to maintain precision
* Close connections properly on page unload
***
## See It Live
**Try the API without writing code.** The interactive streaming demo lets you pick a chain, pick tokens (or paste a custom contract address), and watch SSE events flow in real time. You can also copy the generated subscription payload to use in your own code.
**What you'll see:**
* Real-time price updates, sub-second latency
* Multi-chain support (switch chains in the UI)
* Connection status, latency, and update counts
* The exact JSON the server pushes, line by line
***
## Next Steps
Congratulations! You've built your first streaming application. Here's what to explore next:
What block-level reserve updates contain, both subscription methods, annotated wire example, and use cases for LPs, MEV builders, and analytics teams.
Build streaming components for React and Next.js applications.
The POST /sse/prices endpoint for multi-token price subscriptions.
The POST /sse/reserves endpoint for multi-pool and multi-token subscriptions.
***
## Get Help
Get help from our community.
Read the complete API documentation.
# All DexPaprika tools, SDKs and integrations
Source: https://docs.dexpaprika.com/tools/all-tools
Every official DexPaprika tool in one place: MCP servers, SDKs for TypeScript, Python, Go and PHP, the Rust CLI, the n8n node, the LangChain package, the Claude plugin, and agent skills. Install commands for each.
Everything official we ship for DexPaprika, with the install command for each. All of it reads the same API: 36 blockchains, 230+ DEXes, 36M+ pools and 33M+ tokens.
Version numbers are deliberately not listed here, because they go stale faster than a docs page does. Each link goes to the canonical registry, which always shows the current release.
## Which one do I want?
| If you want to | Use |
| --------------------------------------- | ---------------------------------------------------------------- |
| Give an AI agent live DEX data | [Hosted MCP server](#hosted-mcp-server) |
| Write code in TS, Python, Go or PHP | [SDKs](#sdks) |
| Query from a terminal or a shell script | [CLI](#cli) |
| Build a no-code automation | [n8n community node](#n8n) |
| Build a Python agent chain | [LangChain package](#langchain) |
| Work in Claude Code | [Claude plugin](#claude-plugin) or [agent skills](#agent-skills) |
| Stream prices or reserves live | [Streaming API](#streaming) |
| Call the API directly | [REST API reference](/api-reference/introduction) |
## MCP servers
Two ways to connect an AI agent. Both expose the same data; the hosted server needs no install.
### Hosted MCP server
Nothing to install. Point any MCP client at the URL:
```json theme={null}
{ "mcpServers": { "dexpaprika": { "url": "https://mcp.dexpaprika.com/sse" } } }
```
Streamable HTTP is also available at `https://mcp.dexpaprika.com/streamable-http`. See the [hosted MCP server guide](/ai-integration/hosted-mcp-server), the [full tools reference](/ai-integration/mcp-tools), and the [client setup pages](/ai-integration/mcp-clients) for Cursor, VS Code, Codex and others.
### Self-hosted MCP server
Runs locally over stdio, published on npm as [`dexpaprika-mcp`](https://www.npmjs.com/package/dexpaprika-mcp):
```bash theme={null}
npx dexpaprika-mcp
```
Setup and the tool list are on the [MCP server page](/ai-integration/mcp). The self-hosted build exposes the read tools; the hosted server adds feedback submission.
## SDKs
| Language | Package | Install |
| ---------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------- |
| TypeScript | [`dexpaprika-sdk`](https://www.npmjs.com/package/dexpaprika-sdk) | `npm install dexpaprika-sdk` |
| Python | [`dexpaprika-sdk`](https://pypi.org/project/dexpaprika-sdk/) | `pip install dexpaprika-sdk` |
| Go | [`dexpaprika-sdk-go`](https://github.com/coinpaprika/dexpaprika-sdk-go) | `go get github.com/coinpaprika/dexpaprika-sdk-go` |
| PHP | [`coinpaprika/dexpaprika-sdk`](https://packagist.org/packages/coinpaprika/dexpaprika-sdk) | `composer require coinpaprika/dexpaprika-sdk` |
Quickstarts: [TypeScript](/get-started/sdk-ts), [Go](/get-started/sdk-go), [Python](/get-started/sdk-python), [PHP](/get-started/sdk-php).
## CLI
A Rust CLI with real-time SSE streaming built in, on [crates.io](https://crates.io/crates/dexpaprika-cli):
```bash theme={null}
cargo install dexpaprika-cli
```
See the [CLI tutorial](/tutorials/cli) for commands and output formats.
## Agent and automation integrations
### n8n
The [`n8n-nodes-dexpaprika`](https://www.npmjs.com/package/n8n-nodes-dexpaprika) community node gives you typed Token, Pool and Network operations, and works as an AI Agent tool. On self-hosted n8n, go to **Settings > Community Nodes > Install** and enter:
```
n8n-nodes-dexpaprika
```
Full setup, plus the no-install route using n8n's built-in MCP Client Tool, is on the [n8n page](/ai-integration/n8n).
### LangChain
[`langchain-dexpaprika`](https://pypi.org/project/langchain-dexpaprika/) provides ready-made tools and a toolkit:
```bash theme={null}
pip install langchain-dexpaprika
```
See the [LangChain page](/ai-integration/langchain). For CrewAI and the Vercel AI SDK, both of which consume the hosted MCP server directly, see [CrewAI](/ai-integration/crewai) and [Vercel AI SDK](/ai-integration/vercel-ai-sdk).
### Claude plugin
Adds the MCP server, a DeFi data analyst agent, and four analysis skills to Claude Code:
```
/plugin marketplace add coinpaprika/claude-marketplace
/plugin install dexpaprika@coinpaprika-plugins
```
Walkthrough on the [Claude Code plugin guide](/ai-integration/claude-code-plugin-guide).
### Agent skills
Portable skills that teach any agent the endpoints and response shapes, from [coinpaprika/skills](https://github.com/coinpaprika/skills):
```bash theme={null}
npx skills add github.com/coinpaprika/skills/
```
Skills do not auto-update: the installed copy is a snapshot, so re-run the command to refresh it. Details on the [skills page](/ai-integration/skills).
### Agent landing page
[agents.dexpaprika.com](https://agents.dexpaprika.com) is a single markdown-first page written for agents to read directly, with the endpoint list and streaming quickstart. The raw skill file is at [dexpaprika.com/agents/skill.md](https://dexpaprika.com/agents/skill.md).
## Streaming
Server-sent events for live token prices and pool reserves at `streaming.dexpaprika.com`. Start with the [streaming introduction](/streaming/introduction) and the [quick start](/streaming/tutorials/quick-start).
Each update delivered over a stream costs one credit, the same as a REST call. Because updates are pushed only when a swap moves the price, budget for the update rate of what you subscribe to rather than the number of connections. See [rate limits](/knowledge-base/rate-limits) and [plan your credit usage](/knowledge-base/credit-usage).
## Where else you can find us
DexPaprika's MCP server is listed in the [official MCP registry](https://registry.modelcontextprotocol.io) as `com.dexpaprika/dexpaprika`, on [Glama](https://glama.ai/mcp/servers/coinpaprika/dexpaprika-mcp), and on [Smithery](https://smithery.ai/servers/coinpaprika/dexpaprika).
## Getting started
No API key is needed to start: 50,000 credits a month keyless. A [free registered key](https://console.dexpaprika.com/dashboard) raises that to 300,000, and Pro to 5,000,000 with 300 requests a minute instead of 30. See [rate limits](/knowledge-base/rate-limits) and [API Pro](/api-pro/introduction).
# Coverage checker
Source: https://docs.dexpaprika.com/tools/coverage-checker
Use this tool to quickly verify whether an asset exists in DexPaprika’s dataset. Enter a contract address, token name, or ticker symbol; we’ll search our coverage and show the closest matches.
See also: [Networks](/api-reference/networks/get-a-list-of-available-blockchain-networks),
[Liquidity pool endpoint](/api-reference/pools/get-a-pool-on-a-network),
[Token data](/api-reference/tokens/get-a-tokens-latest-data-on-a-network)
### FAQs
Copy the token or pool address and pass it to the corresponding REST endpoints along with the network.
Yes, by the same limits as any other call: 30 requests a minute, against 50,000 credits a month keyless or 300,000 with a free key. See [rate limits](/knowledge-base/rate-limits).
Yes. Provide the `network` when calling token/pool endpoints.
### Next steps
Get pool reserves and metricsFetch swaps/adds/removes
# Batch Token Prices Efficiently
Source: https://docs.dexpaprika.com/tutorials/batch-token-prices
Fetch prices for up to 10 tokens in one call with the multi-asset endpoint, stay under the per-minute request limit, and understand why batching does not reduce credit spend.
## Tutorial overview
Efficiently fetch real-time prices for up to 10 tokens in a single API request using the DexPaprika **multi-asset endpoint**. This guide shows you why batching matters and how to implement it effectively.
The multi-asset endpoint is optimized for efficiency. Learn why batching is critical for sustainable API usage and how to structure your requests.
***
## Why batch requests? The math behind it
### Single request vs. batching
Let's say you need to monitor prices for **50 tokens** on Ethereum.
**Scenario 1: individual requests**
```
50 tokens × 1 request per token = 50 API calls
```
**Scenario 2: using the multi-asset endpoint**
```
50 tokens ÷ 10 tokens per batch = 5 API calls
```
**Result: 90% fewer HTTP calls.** The credit cost is unchanged: 50 tokens cost 50 credits either way, because batch endpoints bill one credit per item.
### Real-world impact
| Use case | Tokens to monitor | Individual HTTP calls | Batched HTTP calls | Reduction in HTTP calls |
| ------------------------- | ----------------- | --------------------- | ------------------ | ----------------------- |
| Portfolio tracker | 20 tokens | 20 calls | 2 calls | 90% |
| DeFi aggregator | 100 tokens | 100 calls | 10 calls | 90% |
| Trading bot (30s updates) | 50 tokens/day | \~2,880 calls/day | \~288 calls/day | 90% |
| Real-time dashboard | 35 tokens | 35 calls | 4 calls | 88.6% |
**The key insight:** the multi-asset endpoint cuts your HTTP call count by up to **90%**, which is what the per-minute rate limit counts. It does not cut credits, which is what the monthly allowance counts. Batch for latency and for per-minute headroom, and size your monthly plan on the number of tokens, not the number of calls.
***
## Understanding rate limits
Two limits apply, and batching only helps with one of them. The **per-minute request limit** counts HTTP calls: 30 a minute on the free tier, [300 on Pro](https://dexpaprika.com/api/pricing). Batching cuts HTTP calls, so it buys you real headroom there. The **monthly credit allowance** counts items: a batch of 10 tokens costs 10 credits, exactly what 10 separate calls would cost. See [rate limits](/knowledge-base/rate-limits).
By batching requests:
* **Reduce server load** by opening fewer HTTP connections
* **Improve latency** with a single network round-trip for multiple tokens
* **Stay under the per-minute limit**: 5 calls instead of 50 is 5 against your 30-a-minute budget
* **Scale comfortably** and monitor hundreds of tokens without piling up connections
***
## Step 1: Understanding the endpoint
The multi-asset pricing endpoint accepts:
* **Network** (path parameter): e.g., `ethereum`, `solana`, `base`
* **Tokens** (query parameter): Comma-separated list of up to 10 token addresses
* **Constraint:** Maximum 10 tokens per request, 2000 character limit
```bash theme={null}
GET /networks/{network}/multi/prices?tokens={token1},{token2},...,{token10}
```
***
## Step 2: Simple batch request
Fetch prices for 3 tokens on Ethereum in a single request:
```bash theme={null}
curl -X GET "https://api.dexpaprika.com/networks/ethereum/multi/prices?tokens=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2,0xdac17f958d2ee523a2206206994597c13d831ec7,0x6b175474e89094c44da98b954eedeac495271d0f" | jq
```
Response:
```json Response [expandable] theme={null}
[
{
"id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"chain": "ethereum",
"price_usd": 4400.57
},
{
"id": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"chain": "ethereum",
"price_usd": 1.0003
},
{
"id": "0x6b175474e89094c44da98b954eedeac495271d0f",
"chain": "ethereum",
"price_usd": 1.0001
}
]
```
Duplicate input addresses may produce duplicate entries in the response. Dedupe client-side if required.
***
## Step 3: Batching larger token lists in Node.js
If you need to monitor more than 10 tokens, split them into chunks:
```javascript theme={null}
const https = require('https');
const tokenAddresses = [
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH
"0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT
"0x6b175474e89094c44da98b954eedeac495271d0f", // DAI
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
"0x2260fac5e5542a773aa44fbcff9fafb9a190d659", // WBTC
"0x7fc66500c84a76ad7e9c93437e434122a1b63cad", // AAVE
"0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", // UNI
"0x6982508145454ce325ddbe47a25d4ec3d2311933", // PEPE
"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", // bbb (example)
"0xcccccccccccccccccccccccccccccccccccccccc", // ccc (example)
"0xdddddddddddddddddddddddddddddddddddddddd", // ddd (example)
"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", // eee (example)
];
// Split into chunks of 10
function chunkArray(arr, size) {
const chunks = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
}
async function fetchPricesInBatches(network, tokens) {
const chunks = chunkArray(tokens, 10);
const allPrices = [];
for (const chunk of chunks) {
const tokensParam = chunk.join(',');
const url = `https://api.dexpaprika.com/networks/${network}/multi/prices?tokens=${tokensParam}`;
try {
const prices = await new Promise((resolve, reject) => {
https.get(url, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
resolve(JSON.parse(data));
});
}).on('error', reject);
});
allPrices.push(...prices);
console.log(`Fetched batch: ${chunk.length} tokens`);
} catch (error) {
console.error(`Error fetching batch:`, error);
}
}
return allPrices;
}
// Usage
(async () => {
const prices = await fetchPricesInBatches('ethereum', tokenAddresses);
console.log(`\nTotal tokens with prices: ${prices.length}`);
prices.forEach(token => {
console.log(`${token.id}: $${token.price_usd}`);
});
})();
```
### Axios variant (node.js)
```javascript theme={null}
import axios from 'axios';
async function fetchBatch(network, addresses) {
const tokensParam = addresses.join(',');
const url = `https://api.dexpaprika.com/networks/${network}/multi/prices`;
const { data } = await axios.get(url, { params: { tokens: tokensParam }, timeout: 5000 });
return data; // [{ id, chain, price_usd }, ...]
}
```
**Key Observation:** 12 tokens require only 2 API calls instead of 12! This is an **83% reduction** in network requests.
***
## Step 4: Python implementation with real-time monitoring
Build a simple price monitor that updates every 30 seconds:
```python theme={null}
import requests
import time
from typing import List, Dict
class TokenPriceMonitor:
def __init__(self, network: str = 'ethereum'):
self.network = network
self.base_url = f"https://api.dexpaprika.com/networks/{network}/multi/prices"
self.batch_size = 10
self.prices_cache = {}
def chunk_tokens(self, tokens: List[str]) -> List[List[str]]:
"""Split tokens into batches of max 10"""
return [tokens[i:i + self.batch_size] for i in range(0, len(tokens), self.batch_size)]
def fetch_prices(self, tokens: List[str]) -> Dict[str, float]:
"""Fetch prices for multiple tokens in batches"""
batches = self.chunk_tokens(tokens)
results = {}
for batch in batches:
tokens_param = ','.join(batch)
try:
response = requests.get(
self.base_url,
params={'tokens': tokens_param},
timeout=5
)
response.raise_for_status()
data = response.json()
for token in data:
results[token['id']] = token['price_usd']
except requests.RequestException as e:
print(f"Error fetching batch: {e}")
# Optional: basic backoff before next batch to avoid tight retry loops
time.sleep(1)
return results
def monitor_prices(self, tokens: List[str], interval: int = 30):
"""Continuously monitor token prices"""
print(f"Monitoring {len(tokens)} tokens on {self.network}")
print(f"Total API calls per update: {len(self.chunk_tokens(tokens))}")
try:
while True:
prices = self.fetch_prices(tokens)
print(f"\n[{time.strftime('%Y-%m-%d %H:%M:%S')}] Updated {len(prices)} tokens")
for token, price in list(prices.items())[:5]:
print(f" {token}: ${price:.6f}")
if len(prices) > 5:
print(f" ... and {len(prices) - 5} more tokens")
time.sleep(interval)
except KeyboardInterrupt:
print("\nMonitoring stopped.")
# Example usage
if __name__ == "__main__":
monitor = TokenPriceMonitor(network='ethereum')
tokens_to_watch = [
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", # WETH
"0xdac17f958d2ee523a2206206994597c13d831ec7", # USDT
"0x6b175474e89094c44da98b954eedeac495271d0f", # DAI
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", # USDC
"0x2260fac5e5542a773aa44fbcff9fafb9a190d659", # WBTC
]
monitor.monitor_prices(tokens_to_watch)
```
***
## Step 5: Error handling & edge cases
### Handling Invalid or Unpriced Tokens
Some tokens may not have prices. The endpoint returns only valid tokens:
```bash theme={null}
# Request with some invalid tokens
curl -X GET "https://api.dexpaprika.com/networks/ethereum/multi/prices?tokens=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2,0xdeaddeaddeaddeaddeaddeaddeaddeaddaddadd" | jq
```
Response (only the priced token is returned):
```json theme={null}
[
{
"id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"chain": "ethereum",
"price_usd": 4400.57
}
]
```
### Edge-case requests
```bash theme={null}
# 0 tokens (expect 400)
curl -s -o /dev/stderr -w "%{http_code}\n" "https://api.dexpaprika.com/networks/ethereum/multi/prices"
# 11 tokens (expect 400)
curl -s -o /dev/stderr -w "%{http_code}\n" "https://api.dexpaprika.com/networks/ethereum/multi/prices?tokens=0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xA,0xB"
```
**Best Practice:** Always validate the response. Check that the number of returned tokens matches your expectations. Handle missing tokens gracefully in your application.
***
## Step 6: Comparison: Single vs. batch requests
### Request 100 tokens for a dashboard
**Without batching:**
```bash theme={null}
for token in "${token_addresses[@]}"; do
curl -X GET "https://api.dexpaprika.com/networks/ethereum/tokens/$token"
done
# Result: 100 API calls
```
**With batching:**
```bash theme={null}
# Call 1
curl -X GET "https://api.dexpaprika.com/networks/ethereum/multi/prices?tokens=token1,token2,...,token10"
# Call 2
curl -X GET "https://api.dexpaprika.com/networks/ethereum/multi/prices?tokens=token11,token12,...,token20"
# ... Continue for remaining batches ...
# Result: 10 API calls
```
**Impact:**
* **90% fewer HTTP calls**, so 90% less pressure on the per-minute limit
* **10x faster execution** (a single network round-trip per batch against 100 individual calls)
* **Lower latency** for your users
* **Same credit cost.** 100 tokens is 100 credits whichever way you fetch them
***
## Performance metrics
**Benchmark: Fetching prices for 50 tokens on Ethereum**
| Metric | Single requests | Batched (5 calls) |
| ------------------------------------ | --------------- | ----------------- |
| HTTP calls | 50 | 5 |
| Credits spent | 50 | 50 |
| Total Network Round-trips | 50 | 5 |
| Estimated Time (assuming 100ms/call) | 5,000ms | 500ms |
| Bandwidth Used | \~50KB | \~5KB |
| Server Load | High | Low |
**Result:** batching gives **10x faster responses** and cuts server load and bandwidth by 90%. Credit spend is unchanged.
***
## Next steps
Explore the complete multi-asset endpoint documentation.
Learn how to fetch detailed data for individual tokens.
***
## Best practices summary
1. **Always batch.** Use the multi-asset endpoint instead of individual calls
2. **Chunk large lists.** Split more than 10 tokens into multiple batches (max 10 per call)
3. **Validate responses.** Not all requested tokens may have prices, so handle missing data
4. **Cache results.** Store prices and update on a schedule instead of on every request
5. **Handle errors gracefully.** Network timeouts and invalid tokens are normal, so retry with backoff
6. **Monitor performance.** Track API call counts to measure your optimization improvements
***
## Get support
Connect with our community and get real-time support.
Share your experience and help us improve.
### FAQs
10 tokens per request. Requests with more than 10 tokens will return HTTP 400.
Duplicate inputs may yield duplicate entries in the response. Dedupe client-side if needed.
Tokens without prices are silently omitted from the response. Only priced tokens are returned.
No. The response order is not guaranteed to match your input order. Use the `id` field to identify each token.
No. Each batch must be for a single network. Use separate calls for different networks.
It depends on your use case. For dashboards, every 30-60 seconds is common. For trading bots, every 1-5 seconds. Batching enables frequent updates without excessive API calls.
Yes. The single token endpoint (`GET /networks/{network}/tokens/{token_address}`) provides detailed metadata like liquidity, volume, and holdings. Use it for deep analysis; use multi-asset for just prices.
No. At least one valid token is required. The endpoint returns HTTP 400 if the tokens list is empty.
# DexPaprika CLI: DEX data from your terminal
Source: https://docs.dexpaprika.com/tutorials/cli
Install the DexPaprika CLI with one command. Query token prices, pool data, OHLCV history, transactions, and stream live prices from your terminal. Supports macOS, Linux, and Windows.
## What is the DexPaprika CLI?
A fast, standalone command-line tool for querying on-chain DEX data. Written in Rust. Covers the entire DexPaprika API: token prices, pools, OHLCV, transactions, search, pool filtering, and real-time price streaming via SSE.
No API key needed to start. No dependencies. One binary.
**Source:** [github.com/coinpaprika/dexpaprika-cli](https://github.com/coinpaprika/dexpaprika-cli)
***
## Install
### One-line install (macOS, Linux)
```bash theme={null}
curl -sSL https://raw.githubusercontent.com/coinpaprika/dexpaprika-cli/main/install.sh | sh
```
Downloads the latest release binary for your platform and puts it in `~/.local/bin`. You can override the install path:
```bash theme={null}
DEXPAPRIKA_INSTALL_DIR=/usr/local/bin curl -sSL https://raw.githubusercontent.com/coinpaprika/dexpaprika-cli/main/install.sh | sh
```
### Download from GitHub
Pre-built binaries for every platform are on the [releases page](https://github.com/coinpaprika/dexpaprika-cli/releases):
| Platform | Binary |
| --------------------- | ------------------------------------------------ |
| macOS (Apple Silicon) | `dexpaprika-cli-{version}-darwin-aarch64.tar.gz` |
| macOS (Intel) | `dexpaprika-cli-{version}-darwin-x86_64.tar.gz` |
| Linux (x86\_64) | `dexpaprika-cli-{version}-linux-x86_64.tar.gz` |
| Linux (ARM64) | `dexpaprika-cli-{version}-linux-aarch64.tar.gz` |
| Windows (x86\_64) | `dexpaprika-cli-{version}-windows-x86_64.zip` |
### Build from source
Requires [Rust](https://rustup.rs/):
```bash theme={null}
cargo install --git https://github.com/coinpaprika/dexpaprika-cli
```
### Verify installation
```bash theme={null}
dexpaprika-cli status
```
You should see a health check response confirming the API is reachable.
***
## Commands
Every DexPaprika REST API endpoint has a matching CLI command:
| Command | What it does | Example |
| -------------- | -------------------------------------------------- | --------------------------------------------------------------------------------- |
| `stats` | Ecosystem overview (total networks, pools, tokens) | `dexpaprika-cli stats` |
| `networks` | List all supported chains | `dexpaprika-cli networks` |
| `dexes` | DEXes on a network | `dexpaprika-cli dexes ethereum` |
| `pools` | Top pools on a network | `dexpaprika-cli pools ethereum --limit 5` |
| `pool` | Single pool details | `dexpaprika-cli pool ethereum 0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640` |
| `dex-pools` | Pools on a specific DEX | `dexpaprika-cli dex-pools ethereum uniswap_v3` |
| `transactions` | Recent swaps for a pool | `dexpaprika-cli transactions ethereum 0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640` |
| `pool-ohlcv` | Historical OHLCV candles | `dexpaprika-cli pool-ohlcv ethereum 0x88e6... --start 2025-01-01` |
| `token` | Token price and details | `dexpaprika-cli token ethereum 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2` |
| `token-pools` | All pools containing a token | `dexpaprika-cli token-pools ethereum 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2` |
| `prices` | Batch token prices (up to 10) | `dexpaprika-cli prices ethereum --tokens 0xc02a...,0xdac1...` |
| `search` | Search tokens, pools, DEXes | `dexpaprika-cli search uniswap` |
| `stream` | Real-time SSE price stream | `dexpaprika-cli stream ethereum 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2` |
| `status` | API health check | `dexpaprika-cli status` |
| `attribution` | License attribution snippets | `dexpaprika-cli attribution` |
| `onboard` | Quick start walkthrough | `dexpaprika-cli onboard` |
| `shell` | Interactive REPL mode | `dexpaprika-cli shell` |
`dex-pools` reads pool search with a `dex_name` filter. The REST path it used to call,
`GET /networks/{network}/dexes/{dex}/pools`, was removed and returns `410 Gone`, so an older
build of the CLI fails on that command until you upgrade. The command name and arguments are
unchanged; in `--raw` JSON the rows arrive under `results` with `has_next_page` and
`next_cursor`, and the 24h volume field is `volume_usd_24h`.
***
## Output formats
The CLI outputs tables by default. Switch to JSON for scripting and piping:
```bash theme={null}
# Table output (default, human-readable)
dexpaprika-cli pools ethereum
# JSON with metadata wrapper
dexpaprika-cli --output json pools ethereum
# Raw JSON (no _meta wrapper, for piping to jq)
dexpaprika-cli --output json --raw pools ethereum
```
Pipe raw JSON into `jq` for filtering:
```bash theme={null}
dexpaprika-cli --output json --raw token ethereum 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 | jq '.summary.price_usd'
```
***
## Examples
### Get a token price
```bash theme={null}
dexpaprika-cli token solana So11111111111111111111111111111111111111112
```
### Search for a token you don't know the address of
```bash theme={null}
dexpaprika-cli search jupiter
```
### List top pools by volume
```bash theme={null}
dexpaprika-cli pools ethereum --limit 10
```
### Get OHLCV history for a pool
```bash theme={null}
dexpaprika-cli pool-ohlcv ethereum 0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640 --start 2025-01-01
```
### Stream live prices
```bash theme={null}
# Single token -- streams until you hit Ctrl+C
dexpaprika-cli stream ethereum 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
# Stop after 50 price updates
dexpaprika-cli stream ethereum 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 --limit 50
# Multiple tokens from a JSON watchlist file
dexpaprika-cli stream --tokens watchlist.json --limit 100
```
### Interactive REPL
```bash theme={null}
dexpaprika-cli shell
```
Opens an interactive session where you can run commands without typing `dexpaprika-cli` each time.
### Batch prices for a portfolio
```bash theme={null}
dexpaprika-cli prices ethereum --tokens 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2,0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48,0x6b175474e89094c44da98b954eedeac495271d0f
```
***
## Use with AI agents
AI agents running in terminals (Claude Code, Aider, etc.) can install and use the CLI as a tool. The one-liner install works in any shell:
```bash theme={null}
curl -sSL https://raw.githubusercontent.com/coinpaprika/dexpaprika-cli/main/install.sh | sh
```
Once installed, agents can call `dexpaprika-cli` directly with `--output json --raw` for machine-readable output. This is useful when:
* The agent doesn't have MCP support
* You want the agent to script data pipelines
* You need to pipe output into other tools (`jq`, `csvkit`, scripts)
* You're running headless or in CI/CD
**Example agent workflow:**
```bash theme={null}
# Agent searches for a token
dexpaprika-cli --output json --raw search "pepe" | jq '.tokens[0]'
# Agent gets the price
dexpaprika-cli --output json --raw token ethereum 0x6982508145454ce325ddbe47a25d4ec3d2311933 | jq '.summary.price_usd'
# Agent finds the top pool
dexpaprika-cli --output json --raw token-pools ethereum 0x6982508145454ce325ddbe47a25d4ec3d2311933 | jq '.[0]'
```
***
## Troubleshooting
The installer puts the binary in `~/.local/bin`. If that's not in your PATH, add it:
```bash theme={null}
export PATH="$HOME/.local/bin:$PATH"
```
Add that line to your `~/.bashrc`, `~/.zshrc`, or `~/.profile` to make it permanent.
The installer tries `~/.local/bin` first (no sudo needed). If it falls back to `/usr/local/bin`, it will ask for sudo. You can also set a custom directory:
```bash theme={null}
DEXPAPRIKA_INSTALL_DIR=~/bin curl -sSL https://raw.githubusercontent.com/coinpaprika/dexpaprika-cli/main/install.sh | sh
```
Check the network ID (must be lowercase: `ethereum`, not `Ethereum`) and verify the token address. Use `dexpaprika-cli search ` to find the correct network and address.
SSE streams can drop due to network issues. The `stream` command will show an error. Restart it and the CLI handles reconnection. For production use, wrap it in a script with retry logic.
***
## Next steps
Full endpoint documentation with interactive playground
Real-time price streaming docs
Standard API workflows
Build projects with AI + DexPaprika
### FAQs
Not to start. The CLI calls the DexPaprika API keyless. [Create a free key in the console](https://console.dexpaprika.com/dashboard) and set it when you want the higher monthly allowance.
Same as the REST API: 50,000 credits a month without a key, 300,000 with a free key, at 30 requests a minute. Each CLI command makes one API call. `stream` opens a single long-lived SSE connection, and each price update it delivers costs one credit, the same as a REST call. A stream left running all month can outgrow the free allowance, so see [Pro pricing](https://dexpaprika.com/api/pricing) if you need 5,000,000 credits at 300 requests a minute.
The filter endpoint can be accessed via the REST API directly. Check the [pool filtering tutorial](/tutorials/pool-filtering) for examples.
Yes. Download the Windows binary from the [releases page](https://github.com/coinpaprika/dexpaprika-cli/releases). The install script works on macOS and Linux only.
# Build a crypto price alert bot
Source: https://docs.dexpaprika.com/tutorials/crypto-alert-bot
Learn how to create a real-time cryptocurrency price alert system using DexPaprika API and Telegram
The DexPaprika API provides reliable data access. If you find any issues or have suggestions for improvement, please [contact us](mailto:support@coinpaprika.com).
## Overview
This tutorial guides you through building a **real-time cryptocurrency price alert system** that monitors prices using the **DexPaprika API** and sends notifications to your Telegram when price thresholds are met. Perfect for traders and developers who want to stay updated on market movements without constant manual checking.
The complete code for this tutorial is available on [GitHub](https://github.com/coinpaprika/tutorials/tree/main/crypto-alert-bot).
***
## Features
* Track any cryptocurrency available on DexPaprika API
* Set custom price thresholds for buy/sell opportunities
* Get instant alerts when prices go above or below your targets
* Configure check intervals to match your trading strategy
* Receive notifications directly on Telegram
***
## Prerequisites
* Node.js (v14 or higher)
* npm (Node Package Manager)
* A Telegram account
* A Telegram Bot (created using BotFather)
***
## Step 1: Create your Telegram bot
1. Open Telegram and search for "BotFather" (@BotFather)
2. Start a chat and send the command `/newbot`
3. Follow the instructions to create your bot
4. Save the **bot token** BotFather provides you
***
## Step 2: Get your Telegram chat ID
1. Start a conversation with your new bot
2. Send any message to your bot
3. Visit this URL in your browser (replace with your actual token):
```bash theme={null}
https://api.telegram.org/botYOUR_BOT_TOKEN/getUpdates
```
4. Find the `"chat":{"id":XXXXXXXXX,` value in the response - this is your **chat ID**
***
## Step 3: Set up the project
1. Clone the repository or set up a new project:
```bash theme={null}
git clone https://github.com/coinpaprika/tutorials/tree/main/crypto-alert-bot
# OR
mkdir crypto-alert-bot
cd crypto-alert-bot
npm init -y
```
2. Install required dependencies:
```bash theme={null}
npm install axios dotenv node-telegram-bot-api
```
3. Create the following files in your project directory:
* `.env` (configuration file)
* `index.js` (main application)
***
## Step 4: Configure your settings
Create a `.env` file in the project directory with the following parameters:
```
# Telegram Bot Token (Get this from BotFather on Telegram)
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
# Telegram Chat ID (The chat where alerts will be sent)
TELEGRAM_CHAT_ID=your_telegram_chat_id_here
# Cryptocurrency to track (token address)
CRYPTO_TOKEN_ID=So11111111111111111111111111111111111111112
CRYPTO_NETWORK=solana
# Price threshold for alert (in USD)
TARGET_PRICE=135
# Alert type: "above" or "below" - to trigger when price goes above or below target
ALERT_TYPE=above
# How often to check price (in minutes)
CHECK_INTERVAL=1
```
Make sure to replace placeholder values with your actual configuration details.
***
## Step 5: Create the alert bot
Create an `index.js` file with the following code:
```javascript expandable [expandable] theme={null}
require('dotenv').config();
const axios = require('axios');
const TelegramBot = require('node-telegram-bot-api');
// Configuration from .env file
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID;
const CRYPTO_TOKEN_ID = process.env.CRYPTO_TOKEN_ID;
const CRYPTO_NETWORK = process.env.CRYPTO_NETWORK;
const TARGET_PRICE = parseFloat(process.env.TARGET_PRICE);
const ALERT_TYPE = process.env.ALERT_TYPE.toLowerCase();
const CHECK_INTERVAL = parseInt(process.env.CHECK_INTERVAL) * 60 * 1000; // Convert minutes to milliseconds
// Initialize Telegram Bot
const bot = new TelegramBot(TELEGRAM_BOT_TOKEN);
// Validate configuration
if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID || !CRYPTO_TOKEN_ID || !CRYPTO_NETWORK ||
isNaN(TARGET_PRICE) || !ALERT_TYPE || isNaN(CHECK_INTERVAL)) {
console.error('Invalid configuration. Please check your .env file.');
process.exit(1);
}
// Send startup message
bot.sendMessage(TELEGRAM_CHAT_ID,
`🤖 Crypto Alert Bot Started!\n\n` +
`Monitoring: ${CRYPTO_TOKEN_ID} on ${CRYPTO_NETWORK}\n` +
`Alert when price goes ${ALERT_TYPE} $${TARGET_PRICE}\n` +
`Checking every ${CHECK_INTERVAL / 60000} minute(s)`
);
// Variables to track alert state
let alertSent = false;
let lastPrice = 0;
// Main function to check price and send alerts
async function checkPrice() {
try {
// Fetch current price from DexPaprika API
const response = await axios.get(
`https://api.dexpaprika.com/networks/${CRYPTO_NETWORK}/tokens/${CRYPTO_TOKEN_ID}`
);
// Extract price from response
const currentPrice = response.data.summary.price_usd;
lastPrice = currentPrice;
console.log(`Current price of ${CRYPTO_TOKEN_ID}: $${currentPrice}`);
// Check if alert condition is met
let shouldAlert = false;
if (ALERT_TYPE === 'above' && currentPrice > TARGET_PRICE) {
shouldAlert = true;
} else if (ALERT_TYPE === 'below' && currentPrice < TARGET_PRICE) {
shouldAlert = true;
}
// Send alert if condition is met and no alert was sent before
if (shouldAlert && !alertSent) {
const message =
`🚨 PRICE ALERT 🚨\n\n` +
`${response.data.name} (${response.data.symbol})\n` +
`Current Price: $${currentPrice}\n` +
`Target: ${ALERT_TYPE} $${TARGET_PRICE}\n\n` +
`Condition met! 🎯`;
bot.sendMessage(TELEGRAM_CHAT_ID, message);
alertSent = true;
console.log('Alert sent!');
}
// Reset alert flag if price goes back on the other side of the threshold
if ((ALERT_TYPE === 'above' && currentPrice < TARGET_PRICE) ||
(ALERT_TYPE === 'below' && currentPrice > TARGET_PRICE)) {
alertSent = false;
}
} catch (error) {
console.error('Error checking price:', error.message);
// Send error notification if API fails
if (error.response) {
bot.sendMessage(TELEGRAM_CHAT_ID,
`⚠️ Error checking price: ${error.response.status} - ${error.response.statusText}`);
} else {
bot.sendMessage(TELEGRAM_CHAT_ID,
`⚠️ Error checking price: ${error.message}`);
}
}
}
// Run the price check immediately
checkPrice();
// Then set up interval to check regularly
setInterval(checkPrice, CHECK_INTERVAL);
console.log(`Bot is running. Checking ${CRYPTO_TOKEN_ID} every ${CHECK_INTERVAL / 60000} minute(s).`);
```
***
## Step 6: Finding the right token address
Need to track a different token? Use DexPaprika API to find its address:
1. List available networks:
```bash theme={null}
curl -X GET "https://api.dexpaprika.com/networks" | jq
```
2. Search for your token:
```bash theme={null}
curl -X GET "https://api.dexpaprika.com/search?query=YOUR_TOKEN_NAME" | jq
```
***
## Step 7: Running the bot
1. Start the bot:
```bash theme={null}
node index.js
```
2. You'll receive a confirmation message on Telegram.
3. The bot will check prices at your specified interval
4. When your price condition is met, you'll get an alert
***
## Running as a background service
### On Linux/Mac:
```bash theme={null}
npm install -g pm2
pm2 start index.js --name crypto-alert
pm2 save
```
### On Windows:
```bash theme={null}
npm install -g forever
forever start index.js
```
***
## How it works
1. The application connects to DexPaprika API to retrieve real-time token prices
2. It compares the current price against your target threshold
3. When the condition is met, it sends an immediate alert via the Telegram Bot API
4. The process repeats based on your configured check interval
***
## Troubleshooting
* Not receiving messages? Double-check your bot token and chat ID
* Ensure your network/token combination is valid in DexPaprika
* Check console output for any error messages
***
## Next steps
Extend the code to monitor multiple tokens or set different thresholds.
Build a visual interface to manage your alerts and view price history.
### FAQs
It reads `summary.price_usd` from the token details endpoint for the specified `network` and `token_address`.
Start with 1 to 5 minutes depending on volatility. A one-minute poll is about 43,000 requests a month, well inside the 50,000 keyless allowance, and you can watch the running total in the [DexPaprika console](https://console.dexpaprika.com).
Use the Search endpoint or list networks first, then fetch the token by address to verify.
Yes. Query pool details or transactions and set thresholds on `liquidity_usd` or recent `volume_usd`.
**Share Your Extensions!** Built something cool by extending this tutorial? We'd love to see it!
Share your work on our [Discord](https://discord.gg/DhJge5TUGM) - your tutorial might be featured on our website.
Ideas to try: smart trend alerts, multi-token tracking, or historical data analysis.
***
## Get support
Connect with our community and get real-time support.
Share your experience and help us improve.
# Fetching token prices
Source: https://docs.dexpaprika.com/tutorials/fetch-token-price
Learn how to retrieve the price of any token using DexPaprika API with simple curl commands.
## Tutorial overview
Fetch real‑time token prices from the DexPaprika DEX API using a `network` id and `token_address`.
The DexPaprika API provides reliable data access. If you find any issues or have suggestions for improvement, please [contact us](mailto:support@coinpaprika.com).
## Fetching token prices
This tutorial will walk you through retrieving the **latest price of any token** using **DexPaprika API**. We will use simple `cURL` commands to interact with the API.
You can test the API directly in the documentation without writing any code. Visit the [API Reference](/api-reference/introduction) to try it out.
***
## Step 1: Get available networks
To fetch token data, you need the **network ID**. Use this request to get a list of supported blockchain networks:
```bash theme={null}
curl -X GET "https://api.dexpaprika.com/networks" | jq
```
The response will include networks like **Solana, Base, Aptos, Ethereum**, etc. Choose the one you need.
You can find the full list of supported networks in the [Networks API](/api-reference/networks/get-a-list-of-available-blockchain-networks).
***
## Step 2: Find a token address
If you don't have a token address, you can search for it using the API:
```bash theme={null}
curl -X GET "https://api.dexpaprika.com/search?query=YOUR_INPUT"
```
Replace `YOUR_INPUT` with the actual phrase you're looking for. The response will return matching token & pools along with their addresses.
***
## Step 3: Fetch the token price
Once you have the **network ID** and **token address**, use the following request to get the token's price:
```bash theme={null}
curl -X GET "https://api.dexpaprika.com/networks/{network}/tokens/{token_address}"
```
Replace:
* `{network}` with the network (e.g., `solana`, `base`)
* `{token_address}` with the address found in Step 2
The response will return data like this:
```json Response [expandable] theme={null}
{
"id": "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
"name": "Jupiter",
"symbol": "JUP",
"chain": "solana",
"decimals": 6,
"total_supply": 9999979509174084,
"description": "",
"website": "",
"explorer": "",
"added_at": "2024-09-11T04:37:20Z",
"summary": {
"price_usd": 0.6863252359922881,
"fdv": 6863238296.5519485244070243816004,
"liquidity_usd": 25575125.4495078768612017,
"24h": {
"volume": 80207699.45778705,
"volume_usd": 55796800.523819186,
"sell": 106864,
"buy": 57315,
"txns": 164179
},
"6h": {
"volume": 11540575.177305005,
"volume_usd": 8037337.331943456,
"sell": 17801,
"buy": 9926,
"txns": 27727
},
"1h": {
"volume": 2766848.754695,
"volume_usd": 1900837.0877990713,
"sell": 3484,
"buy": 2082,
"txns": 5566
},
"30m": {
"volume": 1394651.1182109998,
"volume_usd": 954794.8829624434,
"sell": 1907,
"buy": 1198,
"txns": 3105
},
"15m": {
"volume": 373588.37757400004,
"volume_usd": 255759.0367338767,
"sell": 742,
"buy": 316,
"txns": 1058
},
"5m": {
"volume": 109317.68508500002,
"volume_usd": 74963.92390747965,
"sell": 265,
"buy": 86,
"txns": 351
}
},
"last_updated": "2025-02-26T13:11:25.858732857Z"
}
```
***
## Step 4: Extract only the price (single token)
If you only need the **token price in USD**, you can filter the response using `jq`:
```bash theme={null}
curl -X GET "https://api.dexpaprika.com/networks/{network}/tokens/{token_address}" | jq '.summary.price_usd'
```
This will return only the price:
```json theme={null}
0.6863252359922881
```
***
## Step 5: Fetch prices for multiple tokens (batch)
If you need prices for multiple tokens at once on the same network, use the batch pricing endpoint:
```bash theme={null}
curl -X GET "https://api.dexpaprika.com/networks/ethereum/multi/prices?tokens=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2,0xdac17f958d2ee523a2206206994597c13d831ec7" | jq
```
Example response:
```json Response [expandable] theme={null}
[
{ "id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "chain": "ethereum", "price_usd": 4400.5697112921535 },
{ "id": "0xdac17f958d2ee523a2206206994597c13d831ec7", "chain": "ethereum", "price_usd": 1.0002529851244053 }
]
```
Notes:
* Provide a comma-separated list in a single `tokens` parameter.
* Unknown or unpriced tokens are omitted from the array.
* Order is not guaranteed.
* You can pass up to 10 tokens per request; more than 10 returns HTTP 400.
Explore the full endpoint docs: [Get batched token prices on a network](/api-reference/tokens/get-batched-token-prices-on-a-network)
***
## Next steps
Jump straight into the API documentation and start making requests within minutes.
Learn more ways to integrate DexPaprika into your applications.
## Get support
Connect with our community and get real-time support.
Share your experience and help us improve.
### FAQs
The `network` id (e.g., `solana`, `ethereum`) and the on‑chain `token_address`.
`summary.price_usd` returns USD. Other quote currencies may be added later; USD is the canonical quote.
Thin pools can be noisy; prefer higher‑volume pools or the token’s main pool from the token response.
Not to start. Keyless requests work at 50,000 credits a month per IP; a [free registered key](https://console.dexpaprika.com) raises that to 300,000.
# Find new crypto pools and token launches
Source: https://docs.dexpaprika.com/tutorials/find-new-pools
Discover newly created liquidity pools across 36 blockchains. Perfect for finding new tokens, arbitrage opportunities, and emerging DeFi projects early.
## Tutorial overview
Discover newly created liquidity pools by searching pools on each network sorted by `created_at`, with activity filters.
Hitting any snags? We've got your back - [reach out](mailto:support@coinpaprika.com) and we'll help you get this working.
## Why monitor new pools?
New liquidity pools are where the action happens in DeFi. They signal new token launches, liquidity migrations, and fresh trading opportunities. Being first to spot them can be a serious advantage.
**What you can catch early:**
* **New token launches** before they hit major trackers
* **Arbitrage opportunities** between pools
* **Liquidity migrations** to better DEXes
* **Emerging projects** before they explode
**TL;DR**: Jump to [Step 2](#step-2-get-newest-pools) if you want to start pulling new pools immediately.
***
## Step 1: Pick your networks
First, see what blockchains you want to monitor using the [Networks API](/api-reference/networks/get-a-list-of-available-blockchain-networks):
```bash theme={null}
curl "https://api.dexpaprika.com/networks" | jq '.[] | {id: .id, name: .display_name}'
```
***
## Step 2: Get newest pools
Here's the money shot - getting pools sorted by creation date using the pool search endpoint:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?order_by=created_at&sort=desc&limit=10&detailed=true" | jq
```
### What these parameters do:
| Parameter | Effect | Pro Tip |
| --------------------- | --------------------------------------------------------- | ---------------------------------------------------------------- |
| `order_by=created_at` | Sort by when pool was made | Only way to find truly new pools |
| `sort=desc` | Newest first | Use `asc` for historical analysis |
| `limit=10` | How many pools back | Max is 100, but 10-20 is usually enough |
| `cursor` | Pagination | Pass `next_cursor` from the previous response for deeper history |
| `detailed=true` | Include full token metadata (symbol, name) in each result | Add it whenever you display token symbols |
### What you get:
```json theme={null}
{
"results": [
{
"id": "0x462229e7fc9e6cab0ebbd643cc6dfef2a5261ee9",
"dex_name": "Uniswap V2",
"created_at": "2025-06-09T09:28:35Z",
"volume_usd_24h": 767.17,
"transactions_24h": 5,
"tokens": [
{ "symbol": "🧸TEDDYS", "name": "🧸TeddySwap" },
{ "symbol": "WETH", "name": "Wrapped Ether" }
]
}
],
"has_next_page": true,
"next_cursor": "eyJvZmZzZXQiOjEwfQ=="
}
```
To screen multiple networks in one call, use the global `GET /pools/search` with a `chains` filter, for example `chains=ethereum,base,solana`.
**Red flags to watch for:**
* Zero transactions after hours (might be a test pool)
* Very low liquidity relative to volume (possible wash trading)
***
## Focus on specific DEXes
Want to monitor just Uniswap or Raydium? First get the DEX list using the [Network DEXes API](/api-reference/dexes/get-a-list-of-available-dexes-on-a-network):
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/dexes" | jq '.dexes[] | {id: .dex_id, name: .dex_name}'
```
Then pass that `dex_id` to the `dex_name` filter on [pool search](/api-reference/pools/advanced-pool-filtering-on-a-specific-network):
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?dex_name=uniswap_v3&order_by=created_at&sort=desc&limit=10&detailed=true" | jq '.results[] | {pool: .id, created: .created_at, tokens: [.tokens[].symbol]}'
```
The older `GET /networks/{network}/dexes/{dex}/pools` was removed and returns `410 Gone`. The DEX
is a query filter now. `dex_name` takes the `dex_id` from the list above, matched
case-insensitively; pass the `dex_name` display name instead and you get an empty `results`
array rather than an error. Rows come back under `results` instead of `pools`.
***
## Multi-chain monitoring
Real pros monitor multiple chains simultaneously. Here's how:
### Ethereum
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?order_by=created_at&sort=desc&limit=5&detailed=true" | jq '.results[] | {chain: "ethereum", pool: .id, created: .created_at, volume: .volume_usd_24h, tokens: [.tokens[].symbol]}'
```
### Solana
```bash theme={null}
curl "https://api.dexpaprika.com/networks/solana/pools/search?order_by=created_at&sort=desc&limit=5&detailed=true" | jq '.results[] | {chain: "solana", pool: .id, created: .created_at, volume: .volume_usd_24h, tokens: [.tokens[].symbol]}'
```
### Base
```bash theme={null}
curl "https://api.dexpaprika.com/networks/base/pools/search?order_by=created_at&sort=desc&limit=5&detailed=true" | jq '.results[] | {chain: "base", pool: .id, created: .created_at, volume: .volume_usd_24h, tokens: [.tokens[].symbol]}'
```
Or all three in one request:
```bash theme={null}
curl "https://api.dexpaprika.com/pools/search?chains=ethereum,solana,base&order_by=created_at&sort=desc&limit=15" | jq '.results[] | {chain: .chain, pool: .id, created: .created_at, volume: .volume_usd_24h}'
```
**Pro tip**: Use different limits based on chain activity. Solana might need `limit=20` because of memecoin amounts, while Ethereum `limit=5` catches the important stuff.
***
## Smart filtering strategies
### Only high-activity pools
Skip the dead pools - focus on ones with real trading:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?order_by=created_at&sort=desc&volume_usd_24h_min=1000&txns_24h_min=10&limit=50" | jq '.results[] | {id: .id, created: .created_at, volume: .volume_usd_24h, txns: .transactions_24h}'
```
### Last 24 hours only
Filter server-side by creation time to get super fresh pools:
```bash theme={null}
# Pools created in the last 24 hours
DAY_AGO=$(python3 -c "import time; print(int(time.time()) - 86400)")
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?order_by=created_at&sort=desc&created_after=$DAY_AGO&limit=100" | jq '.results[] | {id: .id, age: .created_at, volume: .volume_usd_24h}'
```
### New token launches
Look for pools where the tokens themselves are brand new:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?order_by=created_at&sort=desc&limit=20&detailed=true" | jq '.results[] | select(.tokens[] | .added_at > "2025-01-25T00:00:00Z") | {pool: .id, pool_age: .created_at, new_tokens: [.tokens[] | select(.added_at > "2025-01-25T00:00:00Z") | .symbol]}'
```
***
## Production monitoring setup
### Simple monitoring script
Here's a bash script that checks for new pools every 5 minutes:
```bash theme={null}
#!/bin/bash
# Monitor new pools across multiple chains
CHAINS=("ethereum" "solana" "base")
MIN_VOLUME=5000
while true; do
echo "🔍 Checking for new pools at $(date)"
for chain in "${CHAINS[@]}"; do
echo "--- $chain ---"
# Get new pools with decent volume
curl -s "https://api.dexpaprika.com/networks/$chain/pools/search?order_by=created_at&sort=desc&volume_usd_24h_min=$MIN_VOLUME&limit=10&detailed=true" | \
jq '.results[] | {
pool_id: .id,
chain: "'$chain'",
created: .created_at,
volume: .volume_usd_24h,
tokens: [.tokens[].symbol]
}'
done
echo "⏰ Sleeping for 5 minutes..."
sleep 300
done
```
### Alert on high-volume new pools
Catch the big moves automatically:
```bash theme={null}
# Check for new pools with serious volume
NEW_POOLS=$(curl -s "https://api.dexpaprika.com/networks/ethereum/pools/search?order_by=created_at&sort=desc&volume_usd_24h_min=50000&limit=20" | jq '.results[]')
if [ -n "$NEW_POOLS" ]; then
echo "🚨 HIGH VOLUME NEW POOL DETECTED!"
echo "$NEW_POOLS"
# Add your notification here (Discord webhook, Slack, email, etc.)
# curl -X POST "YOUR_DISCORD_WEBHOOK" -d "{\"content\": \"New high-volume pool: $NEW_POOLS\"}"
fi
```
***
## Troubleshooting common issues
### "No new pools found"
Check if you're looking at the right timeframe:
```bash theme={null}
# See when the last pool was created
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?order_by=created_at&sort=desc&limit=1" | jq '.results[0].created_at'
```
### "Too much spam"
Filter out low-quality pools with volume and transaction thresholds:
```bash theme={null}
# More restrictive filtering
curl "https://api.dexpaprika.com/networks/solana/pools/search?order_by=created_at&sort=desc&txns_24h_min=20&volume_usd_24h_min=5000&limit=50" | jq '.results[]'
```
To screen by token valuation (for example, only new tokens under a \$100M FDV), use the token search endpoint, which supports `fdv_usd_max`:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/solana/tokens/search?order_by=created_at&sort=desc&fdv_usd_max=100000000&limit=25" | jq '.results[] | {token: .address, fdv: .fdv_usd, volume: .volume_usd_24h}'
```
### "Missing opportunities"
You might need to check more frequently or cast a wider net:
```bash theme={null}
# Check multiple DEXes on one chain
for dex in uniswap_v3 uniswap_v2 sushiswap; do
echo "=== $dex ==="
curl -s "https://api.dexpaprika.com/networks/ethereum/pools/search?dex_name=$dex&order_by=created_at&sort=desc&limit=3&detailed=true" | jq '.results[0] | {dex: "'$dex'", created: .created_at, tokens: [.tokens[].symbol]}'
done
```
***
## What's next?
Analyze new pools with price history.
Monitor trading activity in real-time.
Get comprehensive pool information and metadata.
Find pools, tokens, and DEXes across all networks.
## Need help?
Share strategies and get help from other builders.
Stuck on something? We'll help you figure it out.
### FAQs
Sort by `created_at` descending and set a reasonable `limit`. Poll periodically and de-duplicate by pool id.
Combine `volume_usd_24h_min` and `txns_24h_min` thresholds; optionally whitelist DEXes or networks.
Use the global `GET /pools/search` with `chains=ethereum,base,solana`, or run parallel per-network requests and merge by a timestamp key.
Yes. Query newest pools and alert when `volume_usd_24h` exceeds your threshold.
# Local crypto analytics with DuckDB
Source: https://docs.dexpaprika.com/tutorials/local-analytics-with-duckdb
Build a powerful, local crypto analytics database with DuckDB. This tutorial guides you through creating an ETL pipeline for Uniswap v3 data to run complex SQL queries instantly.
## Tutorial overview
Create a local DuckDB analytics database from DEX pools and OHLCV for instant SQL over on‑chain data.
## The local powerhouse: A core pattern for on-chain analysis
Why make thousands of slow, rate-limited API calls when you can run complex SQL queries instantly on your own machine? This tutorial introduces the most effective pattern for crypto data analysis: creating a local, high-performance copy of a complete on-chain dataset. By fetching the data once, you unlock unlimited, high-speed analytical capabilities.
Looking for other analytics solutions? Check out our full list of [API Tutorials](/tutorials/tutorial_intro) for more step-by-step guides.
The free tier gives you the full endpoint surface and the full history, not a cut-down dataset. What it caps is throughput (50,000 credits a month keyless, 300,000 with a [free key](https://console.dexpaprika.com), at 30 requests a minute) and freshness (data is served with a delay of up to 15 seconds; [Pro is real time](https://dexpaprika.com/api/pricing)). Pulling once into DuckDB and querying locally is a good fit for that shape.
```mermaid theme={null}
graph TD;
subgraph "Step 1: Data Pipeline";
A["Run Python ETL Script"] --> B{"Fetches Pool & OHLCV Data"};
B --> C["DexPaprika API"];
C --> B;
B --> D["Local DuckDB File (uniswap_v3.db)"];
end
subgraph "Step 2 & 3: Analysis";
D --> E{"Query the Database"};
E --> F["Option A: Directly with SQL"];
E --> G["Option B: AI Assistant via MCP"];
end
```
**The goal:**
By the end of this guide, you will have a local `uniswap_v3.db` file containing all pools and their recent trading history from Uniswap v3 on Ethereum. You will be able to:
1. Run a robust, high-performance ETL script that pulls a complete dataset from the DexPaprika API.
2. Perform complex SQL queries against your local copy, where no API limit applies.
3. Understand a professional workflow for acquiring and analyzing on-chain data.
**Why this is a foundational skill:**
* **Eliminates Rate Limiting:** Instead of thousands of small, repetitive API calls, you perform one efficient batch download.
* **Unlocks True Analytical Power:** Run complex joins, aggregations, and window functions that are impossible with a simple API.
* **Creates a Foundation:** The skills you learn here can be applied to any data source, preparing you for more advanced, real-time analysis.
Create a Python script to fetch complete Uniswap v3 pool and OHLCV data.
Populate your database and run complex SQL queries to find insights.
Connect your database to an AI assistant for natural language queries.
### FAQs
It’s an embedded analytics database: zero server setup, a very fast columnar engine, and a good fit for local SQL over large datasets.
Depends on pools and history length; tens of millions of rows are feasible on a laptop with DuckDB’s compression.
Append new OHLCV daily or hourly depending on your use case; the pipeline is designed for incremental runs.
Yes. Create separate tables per network/DEX and union them, or annotate rows with `network`/`dex` columns.
***
## Step 1: Build your local data pipeline
First, let's create a Python script to act as our ETL (Extract, Transform, Load) pipeline. This script will fetch all pool data for **Uniswap v3 on Ethereum** and their recent trading history, then load it into a local DuckDB database file. It leverages two key endpoints: the [Top Pools on a DEX endpoint](/api-reference/pools/get-top-x-pools-on-a-networks-dex) to discover pools, and the [Pool OHLCV Data endpoint](/api-reference/pools/get-ohlcv-data-for-a-pool-pair) to fetch historical price data.
Create a new file named `build_uniswap_db.py`.
```python build_uniswap_db.py [expandable] theme={null}
import duckdb
import pandas as pd
from datetime import datetime, timedelta
import logging
import os
import asyncio
import aiohttp
from typing import List, Dict
# --- Configuration ---
API_BASE_URL = "https://api.dexpaprika.com"
NETWORK = "ethereum"
DEX_ID = "uniswap_v3"
HISTORY_DAYS = 14 # Default days of OHLCV data to fetch
DB_FILE = "dbs/uniswap_v3.db"
INTERVAL = "1h" # 1-hour intervals
OHLCV_API_LIMIT = 100 # Max records per API call
TOP_POOLS_LIMIT = 500 # Focus on top 500 pools by volume
CONCURRENT_REQUESTS = 3 # Number of concurrent API requests
BATCH_SIZE = 15 # Number of pools to process in each batch
# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
async def fetch_with_retry(session: aiohttp.ClientSession, url: str, params: Dict = None, retries=5, backoff_factor=1.0) -> Dict:
"""Generic async fetch function with exponential backoff."""
for attempt in range(retries):
try:
async with session.get(url, params=params, timeout=30) as response:
response.raise_for_status()
return await response.json()
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
if attempt == retries - 1:
logging.error(f"Final attempt failed for {url}: {e}")
raise
sleep_time = backoff_factor * (2 ** attempt)
logging.warning(f"Request to {url} failed: {e}. Retrying in {sleep_time:.2f}s...")
await asyncio.sleep(sleep_time)
return {}
async def get_top_dex_pools(session: aiohttp.ClientSession, network: str, dex_id: str) -> List[Dict]:
"""Fetches top pools for a given DEX, handling cursor pagination asynchronously."""
logging.info(f"Fetching top {TOP_POOLS_LIMIT} pools for {dex_id} on {network}...")
all_pools = []
cursor = None
while len(all_pools) < TOP_POOLS_LIMIT:
# The DEX is a filter now: /networks/{network}/dexes/{dex}/pools was removed and returns 410.
url = f"{API_BASE_URL}/networks/{network}/pools/search"
# detailed=true is what puts name/symbol/decimals on results[].tokens[];
# the lean default only carries the token id.
params = {"dex_name": dex_id, "limit": 100, "order_by": "volume_usd_24h",
"sort": "desc", "detailed": "true"}
if cursor:
params["cursor"] = cursor
try:
data = await fetch_with_retry(session, url, params=params)
pools = data.get('results', [])
if not pools:
break
all_pools.extend(pools)
logging.info(f"Fetched a page, got {len(pools)} pools. Total: {len(all_pools)}")
if len(all_pools) >= TOP_POOLS_LIMIT:
all_pools = all_pools[:TOP_POOLS_LIMIT]
break
if not data.get('has_next_page'):
break
cursor = data.get('next_cursor')
await asyncio.sleep(0.5) # Be respectful to the API
except Exception as e:
logging.error(f"Error fetching a page of {dex_id} pools: {e}")
break
logging.info(f"Finished fetching pools. Total found: {len(all_pools)}")
return all_pools
async def get_pool_ohlcv(session: aiohttp.ClientSession, pool_address: str, pool_created_at: str, semaphore: asyncio.Semaphore) -> List[Dict]:
"""
Fetches 1-hour OHLCV data for a pool using an intelligent date range and dynamic windowing.
"""
async with semaphore:
logging.info(f"Fetching OHLCV for pool {pool_address}...")
final_end_time = datetime.utcnow()
# Use the later of: pool creation date or the default history window
start_time = final_end_time - timedelta(days=HISTORY_DAYS)
if pool_created_at:
try:
pool_creation = datetime.strptime(pool_created_at, '%Y-%m-%dT%H:%M:%SZ')
if pool_creation > start_time:
start_time = pool_creation
except (ValueError, TypeError):
logging.warning(f"Could not parse creation date '{pool_created_at}', using default {HISTORY_DAYS} days.")
all_ohlcv = []
current_start_time = start_time
# Calculate how much time each API call can cover
interval_hours = 1 # Based on "1h" interval
time_delta_per_call = timedelta(hours=OHLCV_API_LIMIT * interval_hours)
while current_start_time < final_end_time:
batch_end_time = min(current_start_time + time_delta_per_call, final_end_time)
url = f"{API_BASE_URL}/networks/{NETWORK}/pools/{pool_address}/ohlcv"
params = {
"start": current_start_time.strftime('%Y-%m-%dT%H:%M:%SZ'),
"end": batch_end_time.strftime('%Y-%m-%dT%H:%M:%SZ'),
"interval": INTERVAL,
"limit": OHLCV_API_LIMIT
}
try:
batch_data = await fetch_with_retry(session, url, params=params)
if batch_data:
for record in batch_data:
record['network'] = NETWORK
record['pool_address'] = pool_address
avg_price = (record.get('open', 0) + record.get('close', 0)) / 2
record['volume_usd'] = record.get('volume', 0) * avg_price if avg_price > 0 else 0
all_ohlcv.extend(batch_data)
except Exception as e:
logging.warning(f"Could not fetch OHLCV batch for {pool_address}: {e}")
current_start_time = batch_end_time
await asyncio.sleep(0.75) # Small delay to be respectful
logging.info(f"Successfully fetched {len(all_ohlcv)} OHLCV records for {pool_address}")
return all_ohlcv
async def main():
"""Main ETL function to build the local DuckDB database."""
os.makedirs("dbs", exist_ok=True)
async with aiohttp.ClientSession() as session:
pools = await get_top_dex_pools(session, NETWORK, DEX_ID)
all_ohlcv_data = []
semaphore = asyncio.Semaphore(CONCURRENT_REQUESTS)
for i in range(0, len(pools), BATCH_SIZE):
batch = pools[i:i+BATCH_SIZE]
tasks = [get_pool_ohlcv(session, p.get('id'), p.get('created_at'), semaphore) for p in batch if p.get('id')]
batch_num = (i // BATCH_SIZE) + 1
total_batches = (len(pools) + BATCH_SIZE - 1) // BATCH_SIZE
logging.info(f"--- Processing batch {batch_num}/{total_batches} ---")
results = await asyncio.gather(*tasks)
for res in results:
all_ohlcv_data.extend(res)
if i + BATCH_SIZE < len(pools):
logging.info(f"--- Finished batch {batch_num}, sleeping for 10 seconds ---")
await asyncio.sleep(10)
logging.info("ETL process finished. Loading data into DuckDB.")
con = duckdb.connect(database=DB_FILE, read_only=False)
if pools:
for pool in pools:
tokens = pool.get('tokens', [])
pool['token0_symbol'] = tokens[0]['symbol'] if len(tokens) > 0 else None
pool['token1_symbol'] = tokens[1]['symbol'] if len(tokens) > 1 else None
pools_df = pd.DataFrame(pools)
pools_df = pools_df[['id', 'dex_name', 'volume_usd_24h', 'created_at', 'token0_symbol', 'token1_symbol']]
pools_df = pools_df.rename(columns={'id': 'address', 'volume_usd_24h': 'volume_24h_usd'})
con.execute("CREATE OR REPLACE TABLE pools AS SELECT * FROM pools_df")
logging.info(f"Loaded {len(pools_df)} records into 'pools' table.")
if all_ohlcv_data:
ohlcv_df = pd.DataFrame(all_ohlcv_data)
ohlcv_df['timestamp'] = pd.to_datetime(ohlcv_df['time_close'])
ohlcv_df = ohlcv_df[['timestamp', 'network', 'pool_address', 'open', 'high', 'low', 'close', 'volume_usd']]
con.execute("CREATE OR REPLACE TABLE pool_ohlcv AS SELECT * FROM ohlcv_df")
logging.info(f"Loaded {len(ohlcv_df)} records into 'pool_ohlcv' table.")
logging.info("Database build complete. Summary:")
print(con.execute("SHOW TABLES").fetchdf())
print("\nPools Sample:")
print(con.execute("SELECT * FROM pools LIMIT 5").fetchdf())
print("\nOHLCV Sample:")
print(con.execute("SELECT * FROM pool_ohlcv ORDER BY timestamp DESC LIMIT 5").fetchdf())
con.close()
if __name__ == "__main__":
# Ensure you have the required libraries:
# pip install requests pandas duckdb aiohttp
asyncio.run(main())
```
A simple, sequential script is great for learning, but real-world data fetching requires a more robust approach. Here is what we've used to make sure it runs reliably:
* **Asynchronous Operations:** By using `asyncio` and `aiohttp`, the script can make many API requests concurrently instead of one by one. This means shorter time for completion.
* **Dynamic Windowing:** The `get_pool_ohlcv` function calculates how much data to request per API call so that it gets all the data for each pool.
* **Concurrency Control & Throttling:** An `asyncio.Semaphore`, combined with carefully tuned `BATCH_SIZE` and `asyncio.sleep()` calls, makes sure we don't hit the rate limit.
* **Resiliency:** The `fetch_with_retry` function automatically retries failed requests with an exponential backoff delay, making the pipeline resilient to temporary network issues.
### **Required libraries**
Before running the script, make sure you have the necessary Python libraries installed.
```bash theme={null}
pip install requests pandas duckdb aiohttp
```
***
## Step 2: Run the pipeline and query with SQL
Now, execute the script from your terminal. It will fetch all Uniswap v3 pool data from Ethereum and their recent trading history, then create a `uniswap_v3.db` file in a new `dbs` directory. This may take several minutes, but it will be significantly faster than a purely sequential script.
```bash theme={null}
python build_uniswap_db.py
```
### **Querying your new database**
Once the script completes, you have a powerful local database at your fingertips. You can now use any SQL client that supports DuckDB, or Python itself, to perform instant, complex analysis. In step 3, we will connect the database to an AI assistant for natural language queries.
If you want to query the database with a Python script, create a new file named `query_duckdb.py` and paste the following code into it.
```python query_duckdb.py [expandable] theme={null}
import duckdb
import pandas as pd
import time
# Connect to the DuckDB database file
con = duckdb.connect(database='dbs/uniswap_v3.db', read_only=True)
print("=== DuckDB Uniswap v3 Analytics ===\n")
# --- Query 1: Database Summary ---
print("--- 1. Database Summary ---")
pool_count = con.execute("SELECT COUNT(*) FROM pools").fetchone()[0]
ohlcv_count = con.execute("SELECT COUNT(*) FROM pool_ohlcv").fetchone()[0]
print(f"Total Pools Loaded: {pool_count}")
print(f"Total OHLCV Records: {ohlcv_count:,}\n")
# --- Query 2: Top 10 Pools by 24h Volume ---
print("--- 2. Top 10 Pools by 24h Volume ---")
start_time = time.time()
top_pools_df = con.execute("""
SELECT
address,
token0_symbol,
token1_symbol,
volume_24h_usd
FROM pools
ORDER BY volume_24h_usd DESC
LIMIT 10
""").fetchdf()
print(top_pools_df)
print(f"Query executed in {time.time() - start_time:.4f} seconds\n")
# --- Query 3: Peak Trading Hours ---
print("--- 3. Peak Trading Hours (UTC) ---")
start_time = time.time()
hourly_volume_df = con.execute("""
SELECT
EXTRACT(hour FROM timestamp) AS hour_of_day,
SUM(volume_usd) AS total_volume_usd
FROM pool_ohlcv
WHERE volume_usd > 0 AND volume_usd < 1000000000 -- Defensive filter against outliers
GROUP BY hour_of_day
ORDER BY total_volume_usd DESC
""").fetchdf()
# Format the volume for better readability
hourly_volume_df['total_volume_usd'] = hourly_volume_df['total_volume_usd'].map('${:,.2f}'.format)
print(hourly_volume_df)
print(f"Query executed in {time.time() - start_time:.4f} seconds\n")
con.close()
```
Now, execute the script from your terminal:
```bash theme={null}
python query_duckdb.py
```
***
## Step 3: AI-powered analysis with an MCP server
While you can use any SQL client to query your database, the real power comes from connecting it to an AI assistant. By using a Model Context Protocol (MCP) server, you can enable your assistant to directly query the `uniswap_v3.db` file you created. This allows you to ask for insights in plain English instead of writing SQL.
For this, we will use `mcp-server-duckdb`, an open-source MCP server for DuckDB.
### **Install the DuckDB MCP server**
You can install the server easily using `npx`:
```bash theme={null}
npx -y @smithery/cli install mcp-server-duckdb --client claude
```
### **Configure your AI assistant**
Next, you need to tell your AI assistant how to run the server. Add the following to your `claude_desktop_config.json` file.
If you see a "Server disconnected" error after restarting your AI assistant, it means the application cannot find the `uvx` or `npx` command. This happens because the application doesn't share the same `PATH` environment variable as your terminal.
**To fix this, you must use the full, absolute path to the command.**
1. Find the absolute path by running `which uvx` or `which npx` in your terminal.
2. Copy the output (e.g., `/Users/yourname/.local/bin/uvx` or `/opt/homebrew/bin/npx`).
3. Use that full path as the `command` value in the JSON configuration below.
The example below uses `uvx`, which is recommended. Make sure to replace `` with the actual absolute path to your project directory.
```json theme={null}
{
"mcpServers": {
"duckdb-crypto": {
"command": "/Users//.local/bin/uvx",
"args": [
"mcp-server-duckdb",
"--db-path",
"/dbs/uniswap_v3.db",
"--readonly"
]
}
}
}
```
Now, when you start your AI assistant, it will have the tools to query your local Uniswap V3 database. You can ask it things like:
* *"Using the duckdb-crypto tool, find the 5 pools with the highest 24-hour volume."*
* *"What was the hourly volatility for the top pool yesterday?"*
***
## What you've built: From API calls to analytics powerhouse
By completing this tutorial, you have successfully transitioned from being a passive data consumer to an active data analyst. You've replaced the slow, restrictive pattern of making individual API calls with a fast, powerful, and scalable local analytics workflow.
**Key achievements:**
* **Built a professional ETL pipeline:** You have a reusable, high-performance Python script that can create a comprehensive local database from any supported DEX and network.
* **Unlocked high-speed SQL:** You can now perform complex analytical queries on a rich dataset in milliseconds, directly on your machine.
* **Mastered a foundational workflow:** This "local-first" data strategy is a cornerstone of professional data analysis. It enables deeper exploration, from high-level market trends down to individual wallet behaviors.
* **Created a Reusable Asset:** Your `uniswap_v3.db` file is a valuable, reusable asset for any future analysis, dashboarding, or AI integration project.
When your project grows and you need to explore other data solutions, check out our full list of [API Tutorials](/tutorials/tutorial_intro) for more advanced guides.
# Filter and screen liquidity pools
Source: https://docs.dexpaprika.com/tutorials/pool-filtering
Use the DexPaprika pool search endpoint to find pools by volume, liquidity, transaction count, and creation date. Build pool screeners, find high-activity pools, and discover recently launched tokens.
## What you'll build
A pool screener that finds liquidity pools matching specific criteria: volume thresholds, liquidity, transaction counts, and creation dates. This is useful for:
* Finding high-activity pools on any network
* Discovering newly created pools (early token launches)
* Building automated pool monitoring pipelines
* Filtering noise from low-activity pools
This tutorial uses `GET /networks/{network}/pools/search`. It replaces the old `/networks/{network}/pools/filter`, `/networks/{network}/pools`, and `/pools` endpoints, which were removed and now return `410 Gone`. If you used the old endpoints, see the parameter changes below.
***
## The search endpoint
```
GET /networks/{network}/pools/search
```
Combine multiple filters with AND logic. Results are returned in a `results` array with cursor-based pagination. To search across several networks at once, use the global `GET /pools/search` with a `chains` parameter.
### Available parameters
| Parameter | Type | Description |
| ------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `volume_usd_24h_min` / `_max` | number | 24h volume in USD |
| `volume_usd_7d_min` / `_max` | number | 7d volume in USD |
| `volume_usd_30d_min` / `_max` | number | 30d volume in USD |
| `liquidity_usd_min` / `_max` | number | Pool liquidity in USD |
| `txns_24h_min` / `_max` | integer | Transactions in last 24h |
| `price_usd_min` / `_max` | number | Pool price in USD |
| `price_change_percentage_24h_min` / `_max` | number | 24h price change, percent |
| `price_change_percentage_6h_min` / `_max` | number | 6h price change, percent |
| `price_change_percentage_1h_min` / `_max` | number | 1h price change, percent |
| `price_change_percentage_5m_min` / `_max` | number | 5m price change, percent |
| `created_after` / `created_before` | integer | UNIX timestamp window |
| `dex_name` | string | Restrict to a single DEX |
| `token_address` | string | Restrict to pools that contain this token (per-network search only) |
| `order_by` | string | `volume_usd_24h`, `volume_usd_7d`, `volume_usd_30d`, `liquidity_usd`, `txns_24h`, `created_at`, `price_usd`, `price_change_percentage_24h`, `price_change_percentage_6h`, `price_change_percentage_1h`, `price_change_percentage_5m` |
| `sort` | string | `asc` or `desc` |
| `limit` | integer | Items per page (default 10) |
| `cursor` | string | Pass `next_cursor` from the previous response to page forward |
Parameter names migrated from the old filter endpoint: `volume_24h_min` is now `volume_usd_24h_min`, `sort_by` is now `order_by`, `sort_dir` is now `sort`, and `page` is replaced by `cursor`. The `volume_7d`, `volume_30d`, and `liquidity_usd` filters are now functional.
The `token_address` filter works on the per-network `GET /networks/{network}/pools/search` only. The cross-network `GET /pools/search` accepts the parameter but silently ignores it, so a token filter without a network returns unfiltered results.
### Price change windows
Pool search covers four windows: 24h, 6h, 1h and 5m. Each one is both a filter pair and a sort field, so you can screen over the horizon you actually trade on instead of reading a 24h number and guessing what happened in the last few minutes.
A percentage filter is a plain numeric bound, so the minus sign carries the direction. `price_change_percentage_1h_max=-20` means "down by at least 20 percent in the last hour", and `price_change_percentage_1h_min=50` means "up by at least 50 percent". There is no separate direction parameter.
```bash theme={null}
# Ethereum pools that dropped 20 percent or more in the last hour
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?price_change_percentage_1h_max=-20&order_by=price_change_percentage_1h&sort=asc&limit=5"
```
Verify a percentage filter against an unfiltered baseline the first time you use one. An unknown parameter **name** is dropped silently rather than rejected, so a typo like `price_change_1h_max` still returns `200` with a full, unfiltered page of results. Compare the `price_change_percentage_1h` values you get back against a plain `?limit=5` call: unfiltered rows sit near zero, filtered rows respect the bound.
Only the three short windows are pool-only. `GET /networks/{network}/tokens/search` rejects `order_by=price_change_percentage_6h`, `price_change_percentage_1h` and `price_change_percentage_5m` with a `400`, and token rows carry none of those three fields, with or without `detailed=true`. The 24h window works on both: token search sorts by `price_change_percentage_24h` and filters on `price_change_percentage_24h_min` / `_max`.
The short-window **filters** behave worse than the sort fields on the token side. `price_change_percentage_6h_min` is not rejected there, it is dropped, so token search answers `200` with the same unfiltered page you would get without it.
***
## Example 1: High-volume pools on Ethereum
Find Ethereum pools with over \$500,000 in daily volume:
```bash bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?volume_usd_24h_min=500000&order_by=volume_usd_24h&sort=desc&limit=5"
```
```python python theme={null}
import requests
response = requests.get("https://api.dexpaprika.com/networks/ethereum/pools/search", params={
"volume_usd_24h_min": 500000,
"order_by": "volume_usd_24h",
"sort": "desc",
"limit": 5
})
data = response.json()
for pool in data["results"]:
print(f" {pool['dex_name']} | {pool['id'][:16]}... | Vol: ${pool['volume_usd_24h']:,.0f} | Txns: {pool['transactions_24h']}")
if data["has_next_page"]:
print(f"Next page cursor: {data['next_cursor']}")
```
```javascript javascript theme={null}
const response = await fetch(
"https://api.dexpaprika.com/networks/ethereum/pools/search?" +
new URLSearchParams({
volume_usd_24h_min: 500000,
order_by: "volume_usd_24h",
sort: "desc",
limit: 5
})
);
const data = await response.json();
data.results.forEach(pool => {
console.log(` ${pool.dex_name} | ${pool.id.slice(0, 16)}... | Vol: $${pool.volume_usd_24h.toLocaleString()} | Txns: ${pool.transactions_24h}`);
});
```
### Response format
```json theme={null}
{
"results": [
{
"chain": "ethereum",
"id": "0xf6e72db5454dd049d0788e411b06cfaf16853042",
"dex_id": "makerdao",
"dex_name": "MakerDAO",
"volume_usd_24h": 1657645861.93,
"volume_usd_7d": 9412300120.5,
"liquidity_usd": 41250000.0,
"transactions_24h": 4383,
"price_usd": 1.0,
"created_at": "2024-07-11T13:48:47Z",
"tokens": []
}
],
"has_next_page": true,
"next_cursor": "eyJvZmZzZXQiOjV9",
"query": {}
}
```
Response field names: pool address is `id`, transaction count is `transactions_24h`, DEX is `dex_id` (slug) plus `dex_name` (label). Results are in a `results` array, and you page with `next_cursor` rather than page numbers.
***
## Example 2: Recently created pools with activity
Find pools created in the last 7 days that have at least 50 transactions:
```bash bash theme={null}
# Calculate UNIX timestamp for 7 days ago
SEVEN_DAYS_AGO=$(python3 -c "import time; print(int(time.time()) - 7*86400)")
curl "https://api.dexpaprika.com/networks/base/pools/search?created_after=$SEVEN_DAYS_AGO&txns_24h_min=50&order_by=created_at&sort=desc&limit=10"
```
```python python theme={null}
import requests
import time
seven_days_ago = int(time.time()) - 7 * 86400
response = requests.get("https://api.dexpaprika.com/networks/base/pools/search", params={
"created_after": seven_days_ago,
"txns_24h_min": 50,
"order_by": "created_at",
"sort": "desc",
"limit": 10
})
data = response.json()
for pool in data["results"]:
print(f" Created: {pool['created_at']} | DEX: {pool['dex_name']} | Txns: {pool['transactions_24h']} | Vol: ${pool['volume_usd_24h']:,.0f}")
```
***
## Example 3: Paginating through all results
Page with the cursor returned in each response:
```python theme={null}
import requests
all_pools = []
cursor = None
while True:
params = {
"volume_usd_24h_min": 10000,
"txns_24h_min": 20,
"order_by": "volume_usd_24h",
"sort": "desc",
"limit": 100
}
if cursor:
params["cursor"] = cursor
data = requests.get(
"https://api.dexpaprika.com/networks/solana/pools/search", params=params
).json()
all_pools.extend(data["results"])
print(f"Collected {len(all_pools)} pools so far")
if not data["has_next_page"]:
break
cursor = data["next_cursor"]
print(f"Total pools matching criteria: {len(all_pools)}")
```
***
## Example 4: Combining with pool details
Search returns summary data. To get full pool details (token pair info, reserves, fees), make a follow-up request:
```python theme={null}
import requests
# Step 1: Find interesting pools
response = requests.get("https://api.dexpaprika.com/networks/ethereum/pools/search", params={
"volume_usd_24h_min": 100000,
"txns_24h_min": 500,
"limit": 5
})
pools = response.json()["results"]
# Step 2: Get full details for each pool
for pool in pools:
details = requests.get(
f"https://api.dexpaprika.com/networks/{pool['chain']}/pools/{pool['id']}"
).json()
tokens = details.get("tokens", [])
pair = "/".join(t["symbol"] for t in tokens) if tokens else "Unknown"
print(f"{pair} on {pool['dex_name']} -- Vol: ${pool['volume_usd_24h']:,.0f}")
```
***
## Tips
* **Start broad, then narrow:** Begin with just `volume_usd_24h_min` to see what matches, then add more filters.
* **Use `created_after` for new token discovery:** Combine with `txns_24h_min` to find new pools that actually have trading activity.
* **Different networks, different thresholds:** A \$10k volume pool on Ethereum is tiny; on a smaller chain it might be significant. Adjust thresholds per network.
* **Search across networks:** Use `GET /pools/search?chains=ethereum,base,solana` to screen multiple networks in one call.
* **Filter by token:** Pass `token_address` on the per-network search to list only pools containing that token. This replaces the removed `/networks/{network}/tokens/{token_address}/pools` endpoint.
***
## Next steps
More techniques for discovering new pools using the search endpoint
Get full details for any pool including token pairs and reserves
Standard API workflows including search, pricing, and historical data
Full pool details endpoint documentation
### FAQs
It was removed and now returns `410 Gone`, along with `/networks/{network}/pools` and the global `/pools`. Use `/networks/{network}/pools/search` (single network) or `/pools/search` (multiple networks) instead.
Yes. Use the global `GET /pools/search` with a `chains` parameter, for example `chains=ethereum,base`. The per-network `GET /networks/{network}/pools/search` covers a single network.
Set `limit` and read `has_next_page` and `next_cursor` from the response. Pass `next_cursor` back as `cursor` to fetch the next page. There are no page numbers.
Pass `token_address` on the per-network search: `GET /networks/{network}/pools/search?token_address={address}`. This replaces the removed `/networks/{network}/tokens/{token_address}/pools` endpoint, which now returns `410 Gone`. The filter is per-network only; the cross-network `/pools/search` silently ignores it. One token per query: repeating the parameter does not act as a pair filter (the API uses only one of the values, not guaranteed by order), and the old second-token pair filter has no equivalent.
# Scalable time-series analytics with InfluxDB
Source: https://docs.dexpaprika.com/tutorials/real-time-analytics-with-influxdb
Harness InfluxDB, a best-in-class time-series database, to build a powerful crypto analytics pipeline that scales from real-time monitoring to large historical datasets.
## Tutorial overview
Stream and analyze pool OHLCV in InfluxDB with hourly/minute intervals and live Grafana dashboards.
## From real-time monitoring to historical analysis
While InfluxDB is a champion of real-time data, its power extends far beyond live dashboards. It provides a highly optimized engine for storing and querying massive time-series datasets, making it a perfect middle-ground between the local analytics of DuckDB and the enterprise scale of ClickHouse.
Looking for other analytics solutions? Check out our full list of [API Tutorials](/tutorials/tutorial_intro) for more step-by-step guides.
This tutorial will guide you through building a production-grade ETL pipeline to populate InfluxDB with a substantial historical dataset, enabling both high-performance queries and real-time visualization.
```mermaid theme={null}
graph TD;
subgraph "Step 1: Infrastructure";
A["Setup InfluxDB & Grafana (Docker)"];
end
subgraph "Step 2: Data Ingestion";
B["Run Python ETL script"];
C["DexPaprika API"];
D["InfluxDB Bucket ('crypto-data')"];
B --"Fetches 14 days of 1h data for 500 pools"--> C;
B --"Writes time-series data"--> D;
end
subgraph "Step 3 & 4: Analysis & Visualization";
E["Option A: Programmatic queries (Python)"];
F["Option B: Live dashboards (Grafana)"];
D --> E;
D --> F;
end
A --> B;
```
**The goal:**
By the end of this guide, you will have a scalable analytics pipeline that can:
1. Ingest 1-hour OHLCV data for the top 500 Uniswap v3 pools over a 14-day period.
2. Run complex time-series analysis using Python and the Flux language.
3. Visualize the data in a live-updating Grafana dashboard.
Get InfluxDB and Grafana running in seconds with Docker.
Create a robust data pipeline for ingesting large time-series datasets.
Analyze your time-series data with InfluxDB's Python client.
Build a real-time dashboard to monitor crypto pools.
***
## Step 1: Setting Up InfluxDB and Grafana
We'll use Docker Compose to spin up both services. First, create a new directory named `INFLUXDB` in your project root. Inside that directory, create a file named `docker-compose.yml` with the following content.
```yml INFLUXDB/docker-compose.yml theme={null}
services:
influxdb:
image: influxdb:2.7
container_name: influxdb
ports:
- "8087:8086"
volumes:
- influxdb-data:/var/lib/influxdb2
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=my-user
- DOCKER_INFLUXDB_INIT_PASSWORD=my-password
- DOCKER_INFLUXDB_INIT_ORG=my-org
- DOCKER_INFLUXDB_INIT_BUCKET=crypto-data
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-super-secret-token
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
volumes:
influxdb-data:
grafana-data:
```
Run the following command from the root of the project to start the containers:
```bash theme={null}
docker-compose -f INFLUXDB/docker-compose.yml up -d
```
Once running, you can access:
* **InfluxDB UI:** `http://localhost:8087`
* **Grafana UI:** `http://localhost:3000` (login with `admin`/`admin`)
Use the token `my-super-secret-token` to connect to InfluxDB.
We use port `8087` for InfluxDB to avoid potential conflicts with other services that might be using the default port `8086`.
***
## Step 2: Build the Python ETL Pipeline
Create a new file named `INFLUXDB/build_influxdb_db.py`. This script is built to be robust and efficient, capable of ingesting large volumes of time-series data from the DexPaprika API into your InfluxDB instance. It leverages two key endpoints: the [Top Pools on a DEX endpoint](/api-reference/pools/get-top-x-pools-on-a-networks-dex) to discover pools, and the [Pool OHLCV Data endpoint](/api-reference/pools/get-ohlcv-data-for-a-pool-pair) to fetch historical price data.
```python INFLUXDB/build_influxdb_db.py [expandable] theme={null}
import influxdb_client
from influxdb_client.client.write_api import SYNCHRONOUS
import requests
from datetime import datetime, timedelta, timezone
import time
import logging
import asyncio
import aiohttp
from typing import List, Dict
import math
# --- Configuration ---
API_BASE_URL = "https://api.dexpaprika.com"
NETWORK = "ethereum"
DEX_ID = "uniswap_v3"
HISTORY_DAYS = 14 # Fetch 14 days of OHLCV data
TOP_POOLS_LIMIT = 500 # Focus on top 500 pools by volume
BATCH_SIZE = 15 # Process pools in smaller batches
CONCURRENT_REQUESTS = 3 # Concurrent requests for API calls
OHLCV_API_LIMIT = 100 # API limit for OHLCV requests
INTERVAL = "1h" # 1-hour intervals
# InfluxDB Configuration
INFLUX_URL = "http://localhost:8087"
INFLUX_TOKEN = "my-super-secret-token"
INFLUX_ORG = "my-org"
INFLUX_BUCKET = "crypto-data"
# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
async def fetch_with_retry(session: aiohttp.ClientSession, url: str, params: Dict = None, retries=5, backoff_factor=1.0):
"""Generic async fetch function with exponential backoff."""
for attempt in range(retries):
try:
async with session.get(url, params=params, timeout=30) as response:
response.raise_for_status()
return await response.json()
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
if attempt == retries - 1:
logging.error(f"Final attempt failed for {url}: {e}")
raise
sleep_time = backoff_factor * (2 ** attempt)
logging.warning(f"Request to {url} failed: {e}. Retrying in {sleep_time:.2f}s...")
await asyncio.sleep(sleep_time)
return {}
class InfluxDBETL:
def __init__(self):
self.client = influxdb_client.InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
self.write_api = self.client.write_api(write_options=SYNCHRONOUS)
self.api_semaphore = asyncio.Semaphore(CONCURRENT_REQUESTS)
self._ensure_bucket_exists()
def _ensure_bucket_exists(self):
"""Checks if the bucket exists and creates it if not."""
logging.info(f"Ensuring bucket '{INFLUX_BUCKET}' exists...")
buckets_api = self.client.buckets_api()
bucket = buckets_api.find_bucket_by_name(INFLUX_BUCKET)
if not bucket:
logging.warning(f"Bucket '{INFLUX_BUCKET}' not found. Creating it...")
buckets_api.create_bucket(bucket_name=INFLUX_BUCKET, org=INFLUX_ORG)
logging.info(f"Bucket '{INFLUX_BUCKET}' created successfully.")
else:
logging.info(f"Bucket '{INFLUX_BUCKET}' already exists.")
def clear_bucket_data(self):
"""Deletes all data from the 'ohlcv' measurement in the bucket."""
logging.info(f"Clearing existing data from measurement 'ohlcv' in bucket '{INFLUX_BUCKET}'...")
try:
delete_api = self.client.delete_api()
start = "1970-01-01T00:00:00Z"
stop = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
delete_api.delete(start, stop, '_measurement="ohlcv"', bucket=INFLUX_BUCKET, org=INFLUX_ORG)
logging.info("Existing data cleared successfully.")
except Exception as e:
logging.error(f"Could not clear data from bucket: {e}")
async def fetch_top_pools(self) -> List[Dict]:
"""Fetch top pools by volume from the specified DEX, handling cursor pagination."""
logging.info(f"Fetching top {TOP_POOLS_LIMIT} pools for {DEX_ID} on {NETWORK}...")
all_pools = []
cursor = None
async with aiohttp.ClientSession() as session:
while len(all_pools) < TOP_POOLS_LIMIT:
# The DEX is a filter now: /networks/{network}/dexes/{dex}/pools was
# removed and returns 410. detailed=true keeps name/symbol on tokens[].
url = f"{API_BASE_URL}/networks/{NETWORK}/pools/search"
params = {"dex_name": DEX_ID, "limit": 100, "order_by": "volume_usd_24h",
"sort": "desc", "detailed": "true"}
if cursor:
params["cursor"] = cursor
try:
data = await fetch_with_retry(session, url, params=params)
pools = data.get('results', [])
if not pools:
break
all_pools.extend(pools)
logging.info(f"Fetched a page, got {len(pools)} pools. Total: {len(all_pools)}")
if len(all_pools) >= TOP_POOLS_LIMIT:
all_pools = all_pools[:TOP_POOLS_LIMIT]
break
if not data.get('has_next_page'):
break
cursor = data.get('next_cursor')
await asyncio.sleep(0.5) # Be respectful to the API
except Exception as e:
logging.error(f"Error fetching a page of pools: {e}")
break
logging.info(f"Finished fetching pools. Total: {len(all_pools)}")
return all_pools
async def fetch_pool_ohlcv_paginated(self, session: aiohttp.ClientSession, pool_address: str) -> List[Dict]:
"""Fetch complete OHLCV data for a pool using intelligent, dynamic windowing."""
async with self.api_semaphore:
final_end_time = datetime.now(timezone.utc)
current_start_time = final_end_time - timedelta(days=HISTORY_DAYS)
all_ohlcv = []
try:
if 'h' in INTERVAL:
interval_value = int(INTERVAL.replace('h', ''))
time_delta_per_call = timedelta(hours=OHLCV_API_LIMIT * interval_value)
elif 'm' in INTERVAL:
interval_value = int(INTERVAL.replace('m', ''))
time_delta_per_call = timedelta(minutes=OHLCV_API_LIMIT * interval_value)
else:
raise ValueError(f"Unsupported interval format: {INTERVAL}")
except ValueError as e:
logging.error(f"Invalid INTERVAL format: {e}. Defaulting to 1 hour.")
time_delta_per_call = timedelta(hours=OHLCV_API_LIMIT * 1)
total_expected_calls = math.ceil((final_end_time - current_start_time) / time_delta_per_call) if time_delta_per_call.total_seconds() > 0 else 0
call_count = 0
while current_start_time < final_end_time:
call_count += 1
batch_end_time = min(current_start_time + time_delta_per_call, final_end_time)
logging.info(f" [Pool {pool_address}] Fetching window {call_count}/{total_expected_calls}: {current_start_time.date()} to {batch_end_time.date()}")
url = f"{API_BASE_URL}/networks/{NETWORK}/pools/{pool_address}/ohlcv"
params = {
"start": current_start_time.strftime('%Y-%m-%dT%H:%M:%SZ'),
"end": batch_end_time.strftime('%Y-%m-%dT%H:%M:%SZ'),
"interval": INTERVAL,
"limit": OHLCV_API_LIMIT
}
try:
batch_data = await fetch_with_retry(session, url, params=params)
if batch_data:
for record in batch_data:
record['network'] = NETWORK
record['pool_address'] = pool_address
all_ohlcv.extend(batch_data)
except Exception as e:
logging.warning(f"Could not fetch OHLCV batch for {pool_address}: {e}")
current_start_time = batch_end_time
await asyncio.sleep(0.75) # Crucial delay to prevent rate-limiting
logging.info(f"Pool {pool_address}: collected {len(all_ohlcv)} OHLCV records.")
return all_ohlcv
async def fetch_pool_ohlcv_batch(self, pool_addresses: List[str]) -> List[Dict]:
"""Fetch OHLCV data for multiple pools concurrently."""
logging.info(f"Fetching {INTERVAL} OHLCV for {len(pool_addresses)} pools...")
all_ohlcv = []
async with aiohttp.ClientSession() as session:
tasks = [self.fetch_pool_ohlcv_paginated(session, addr) for addr in pool_addresses]
results = await asyncio.gather(*tasks, return_exceptions=True)
for i, result in enumerate(results):
if isinstance(result, list):
all_ohlcv.extend(result)
elif isinstance(result, Exception):
logging.warning(f"OHLCV fetch failed for pool {pool_addresses[i]}: {result}")
return all_ohlcv
def load_ohlcv_data(self, ohlcv_data: List[Dict], pools_map: Dict):
"""Load OHLCV data into InfluxDB."""
if not ohlcv_data:
logging.warning("No OHLCV data to load.")
return
points = []
for record in ohlcv_data:
pool_id = record.get('pool_address')
pair = pools_map.get(pool_id, "Unknown/Unknown")
point = (
influxdb_client.Point("ohlcv")
.tag("pool_id", pool_id)
.tag("pair", pair)
.field("open", float(record['open']))
.field("high", float(record['high']))
.field("low", float(record['low']))
.field("close", float(record['close']))
.field("volume", float(record.get('volume', 0)))
.time(record['time_close'])
)
points.append(point)
if points:
self.write_api.write(bucket=INFLUX_BUCKET, org=INFLUX_ORG, record=points)
logging.info(f"Wrote {len(points)} data points to InfluxDB.")
async def run_etl(self):
"""Run the complete ETL process."""
self.clear_bucket_data()
logging.info(f"Starting InfluxDB ETL process for top {TOP_POOLS_LIMIT} pools...")
pools = await self.fetch_top_pools()
if pools:
pools_map = {
pool['id']: f"{pool['tokens'][0]['symbol']}/{pool['tokens'][1]['symbol']}"
for pool in pools if len(pool.get('tokens', [])) >= 2
}
pool_addresses = [pool['id'] for pool in pools if pool.get('id')]
for i in range(0, len(pool_addresses), BATCH_SIZE):
batch_addresses = pool_addresses[i:i + BATCH_SIZE]
batch_num = (i // BATCH_SIZE) + 1
total_batches = (len(pool_addresses) + BATCH_SIZE - 1) // BATCH_SIZE
logging.info(f"Processing OHLCV batch {batch_num}/{total_batches} ({len(batch_addresses)} pools)")
ohlcv_data = await self.fetch_pool_ohlcv_batch(batch_addresses)
self.load_ohlcv_data(ohlcv_data, pools_map)
if i + BATCH_SIZE < len(pool_addresses):
logging.info(f"--- Finished batch {batch_num}, sleeping for 10 seconds ---")
await asyncio.sleep(10)
logging.info("ETL process completed!")
async def main():
etl = InfluxDBETL()
await etl.run_etl()
if __name__ == "__main__":
# pip install influxdb-client aiohttp requests pandas
asyncio.run(main())
```
This script is built for performance and reliability, using several best practices common in data pipelines:
* **Asynchronous operations:** By using `asyncio` and `aiohttp`, the script can make many API requests concurrently instead of one by one.
* **Dynamic windowing:** The `fetch_pool_ohlcv_paginated` function calculates how much data to request per API call to ensure complete history is fetched efficiently.
* **Concurrency control & throttling:** An `asyncio.Semaphore`, combined with carefully tuned `BATCH_SIZE` and `asyncio.sleep()` calls, keeps the script under the free tier's 30 requests a minute. [Pro raises that to 300 a minute](https://dexpaprika.com/api/pricing), which lets you shorten the sleeps and finish the backfill sooner.
* **Resiliency:** The `fetch_with_retry` function automatically retries failed requests with an exponential backoff delay.
* **Data Integrity:** The script automatically clears old data from the bucket before each run to ensure a clean, consistent dataset.
### **Setup Python Environment**
Before running the ETL script, it's a critical best practice to create an isolated Python environment to manage dependencies.
1. **Create a virtual environment:**
Open your terminal in the project root and run:
```bash theme={null}
python3 -m venv venv
```
2. **Activate the environment:**
* On macOS and Linux:
```bash theme={null}
source venv/bin/activate
```
* On Windows:
```bash theme={null}
.\venv\Scripts\activate
```
Your terminal prompt should now be prefixed with `(venv)`, indicating that the virtual environment is active.
3. **Install required libraries:**
Now, install the necessary Python packages inside the activated environment:
```bash theme={null}
pip install influxdb-client aiohttp requests pandas
```
Run the script to start streaming data into your InfluxDB instance. This may take several minutes.
```bash theme={null}
python INFLUXDB/build_influxdb_db.py
```
***
## Step 3: Programmatic Analysis with Python
While the InfluxDB UI is great for exploration, the real power comes from programmatic access. The `influxdb-client` library for Python allows you to run complex Flux queries and integrate the data into other tools or scripts.
We've created a `query_influxdb.py` script to demonstrate how to connect to your database and perform analysis on the hourly data.
```python INFLUXDB/query_influxdb.py [expandable] theme={null}
import influxdb_client
import pandas as pd
# --- InfluxDB Configuration ---
INFLUX_URL = "http://localhost:8087"
INFLUX_TOKEN = "my-super-secret-token"
INFLUX_ORG = "my-org"
INFLUX_BUCKET = "crypto-data"
def run_flux_query(client: influxdb_client.InfluxDBClient, query: str):
"""Helper function to execute a Flux query and return a pandas DataFrame."""
try:
query_api = client.query_api()
result = query_api.query_data_frame(query, org=INFLUX_ORG)
if isinstance(result, list): # Handle multiple dataframes in result
return pd.concat(result, ignore_index=True) if result else pd.DataFrame()
return result
except Exception as e:
print(f"Error running query: {e}")
return pd.DataFrame()
def main():
"""Connects to InfluxDB and runs sample analytics queries."""
client = influxdb_client.InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
print("=== InfluxDB Python Analytics Demo ===\n")
# --- Query 1: Find available trading pairs ---
print("--- 1. Finding available trading pairs ---")
list_pairs_query = f'''
import "influxdata/influxdb/schema"
schema.tagValues(
bucket: "{INFLUX_BUCKET}",
tag: "pair",
start: -14d
)
'''
pairs_df = run_flux_query(client, list_pairs_query)
if not pairs_df.empty:
available_pairs = pairs_df['_value'].tolist()
print(f"Found {len(available_pairs)} pairs. Examples: {available_pairs[:5]}")
# Use the first available pair for subsequent queries
target_pair = available_pairs[0]
else:
print("No pairs found. Please run the ingestion script first.")
print("Using 'WETH/USDC' as a placeholder for query examples.")
target_pair = "WETH/USDC" # Fallback for demo
print(f"\n--- Using pair '{target_pair}' for next queries ---\n")
# --- Query 2: Get raw data for the target pool ---
print(f"--- 2. Raw OHLCV data for {target_pair} ---")
raw_data_query = f'''
from(bucket: "{INFLUX_BUCKET}")
|> range(start: -3d) // Limit to last 3 days for brevity
|> filter(fn: (r) => r._measurement == "ohlcv")
|> filter(fn: (r) => r.pair == "{target_pair}")
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|> sort(columns: ["_time"], desc: true)
|> limit(n: 10)
'''
raw_df = run_flux_query(client, raw_data_query)
print("Last 10 records:")
if not raw_df.empty and all(c in raw_df.columns for c in ['_time', 'open', 'high', 'low', 'close', 'volume']):
print(raw_df[['_time', 'open', 'high', 'low', 'close', 'volume']])
else:
print("Could not retrieve raw data. Please check if the ingestion was successful.")
print("\n")
# --- Query 3: Calculate 12-hour moving average ---
print(f"--- 3. 12-Hour moving average for {target_pair} close price ---")
moving_avg_query = f'''
from(bucket: "{INFLUX_BUCKET}")
|> range(start: -14d)
|> filter(fn: (r) => r._measurement == "ohlcv" and r._field == "close" and r.pair == "{target_pair}")
|> timedMovingAverage(every: 1h, period: 12h)
|> sort(columns: ["_time"], desc: true)
|> limit(n: 10)
'''
ma_df = run_flux_query(client, moving_avg_query)
print("Last 10 moving average values:")
if not ma_df.empty and all(c in ma_df.columns for c in ['_time', '_value']):
print(ma_df[['_time', '_value']])
else:
print("Could not retrieve moving average data.")
if __name__ == "__main__":
# Ensure you have the required libraries:
# pip install influxdb-client pandas
main()
```
Run the script to see how you can query your data with Python:
```bash theme={null}
python INFLUXDB/query_influxdb.py
```
***
## Step 4: Visualizing Data in Grafana
1. Open Grafana at `http://localhost:3000`.
2. Go to **Connections** > **Add new connection** > **InfluxDB**.
3. Configure the connection:
* **Name**: InfluxDB\_Crypto
* **Query Language**: Flux
* **URL**: `http://influxdb:8086` (use the Docker service name)
* Under **Auth**, toggle **Basic auth** off.
* In the **Custom HTTP Headers** section, add a header:
* **Header**: `Authorization`
* **Value**: `Token my-super-secret-token`
* Enter your InfluxDB **Organization** (`my-org`) and default **Bucket** (`crypto-data`).
4. Click **Save & Test**. You should see a "Bucket found" confirmation.
5. Now, let's create a dashboard. In the left-hand menu, click the **+** icon and select **Dashboard**.
6. Click on the **Add new panel** button.
7. In the new panel view, ensure your `InfluxDB_Crypto` data source is selected at the top.
8. Below the graph, you'll see a query editor. You can switch to the **Script editor** by clicking the pencil icon on the right.
9. Paste the following query into the editor. This query will plot the raw closing price for the `AAVE/USDC` trading pair.
```flux theme={null}
from(bucket: "crypto-data")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "ohlcv" and r._field == "close" and r.pair == "AAVE/USDC")
|> sort(columns: ["_time"], desc: false)
```
**Troubleshooting Tip:** If you initially see "No data," there are two common reasons:
1. **Time Range:** Ensure the time picker at the top right is set to "Last 7 days" or wider, not a shorter period like "Last 6 hours."
2. **Trading Pair:** The default `WETH/USDC` pair used in the original tutorial may not have been in the top 500 pools fetched by the script. The query above uses `AAVE/USDC`, which is more likely to be present. You can find other available pairs by running the `query_influxdb.py` script.
10. At the top right of the dashboard page, set the time range to **Last 7 days** to ensure you see all the historical data you ingested.
11. You should now see the data appear in the panel. You can give the panel a title (e.g., "AAVE/USDC Close Price") in the settings on the right.
12. Click **Apply** to save the panel to your dashboard. You can now add more panels for other queries.
***
## What You've Built
You now have a powerful, scalable analytics pipeline for time-series crypto data. You've combined a production-grade Python ETL script with the industry-standard tools for time-series data storage (InfluxDB) and visualization (Grafana).
**Key achievements:**
* **Built a production-ready ETL pipeline:** You have a reusable, high-performance Python script that can populate a time-series database from any supported DEX.
* **Unlocked programmatic time-series analysis:** You can now perform complex analytical queries on large time-series datasets using Python and Flux.
* **Mastered a scalable analytics workflow:** This pipeline provides a solid foundation for building live dashboards, conducting in-depth market research, and developing sophisticated monitoring or trading algorithms.
* **Enabled live data visualization:** You've connected your database to Grafana, the leading open-source tool for observability and data visualization.
### FAQs
InfluxDB excels at time-series ingestion and dashboards; it’s ideal for hourly/minute data with Grafana visualization.
Keep recent weeks/months for dashboards and archive older data elsewhere; adjust by bucket retention.
`1h` is a sensible default; lower to `15m/5m` for more granularity if needed.
Schedule the ETL to append new windows, and set Grafana panels to auto‑refresh.
# Get token price history and OHLCV data
Source: https://docs.dexpaprika.com/tutorials/retrieve-historical-data
Get historical crypto price data (OHLCV) for any token across 36 blockchains. Perfect for building charts, backtesting strategies, and price analysis.
## Tutorial overview
Pull historical OHLCV from a pool using `start`, `limit`, and `interval`, with tips for smoothing and inverted ratios.
Having trouble with the API? We're here to help - [drop us a line](mailto:support@coinpaprika.com) and we'll get you sorted.
## Why you need historical price data?
Building a crypto app? You'll likely need price charts, volatility analysis, or backtesting data. That's where OHLCV (Open, High, Low, Close, Volume) data comes in - it's the backbone of any serious crypto application.
**Common use cases:**
* **Price charts** in trading apps
* **Backtesting** trading strategies
* **Volatility analysis** for risk management
* **Historical performance** dashboards
* **Market research** and analytics
**Quick Start**: If you know your token already, jump to [Step 2](#step-2-get-price-history-data) to grab the data immediately.
***
## Step 1: Find your token's trading pool
Here's the thing - historical data comes from actual trading pools, not tokens directly. This makes sense because prices happen where people trade.
### Quick search (recommended)
The fastest way to find what you need using the [Search API](/api-reference/search/search-for-tokens-pools-and-dexes):
```bash theme={null}
curl "https://api.dexpaprika.com/search?query=USDC" | jq
```
**Pro tip**: Search returns tokens, pools, and exchanges. Look for pools with high volume - they'll have the most reliable price data.
### If you have the token address
Skip the search and go straight to pools using [pool search](/tutorials/pool-filtering) with the `token_address` filter:
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?token_address=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" | jq
```
The `token_address` filter only works on the per-network `/networks/{network}/pools/search`. The cross-network `/pools/search` accepts the parameter but silently ignores it.
Always pick pools with decent volume (>\$10k daily). Low-volume pools can have weird price spikes that don't reflect real market conditions.
***
## Step 2: Get price history data
Now for the good stuff. Here's how to pull historical OHLCV data using the [Pool OHLCV API](/api-reference/pools/get-ohlcv-data-for-a-pool-pair):
```bash theme={null}
curl "https://api.dexpaprika.com/networks/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/ohlcv?start=2025-01-01&limit=30&interval=24h&inversed=true" | jq
```
*(Using USDC/ETH pool on Ethereum as example in this tutorial)*
### What these parameters do:
| Parameter | What It Does | Example |
| ---------- | ------------------------------ | ------------------------------ |
| `start` | When to start collecting data | `2025-01-01` or Unix timestamp |
| `limit` | How many data points (max 366) | `30` for 30 days |
| `interval` | Time between each point | `24h`, `1h`, `5m`, etc. |
| `end` | When to stop (optional) | `2025-01-31` |
| `inversed` | Flip the price ratio | `true` for ETH/USDC → USDC/ETH |
### What you get back:
```json theme={null}
[
{
"time_open": "2025-01-30T00:00:00Z",
"time_close": "2025-01-31T00:00:00Z",
"open": 3115.1614508315265,
"high": 3277.717757396331,
"low": 3097.803416632386,
"close": 3250.184016286268,
"volume": 226403988
}
]
```
Each data point gives you everything you need for candlestick charts or analysis.
***
## Time intervals that actually matter
Choose based on what you're building:
**For Trading Apps:**
* `1m`, `5m` - Real-time trading
* `1h`, `4h` - Swing trading
* `24h` - Position trading
**For Analytics Dashboards:**
* `24h` - Daily summaries
* Use daily data and aggregate for weekly/monthly views
**For Research:**
* `24h` with longer date ranges (up to 1 year)
***
## Production tips that'll save you time
### Cache aggressively
Historical data doesn't change - cache it locally:
```bash theme={null}
# Check when pool was created to avoid requesting non-existent data
curl "https://api.dexpaprika.com/networks/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640" | jq '.created_at'
```
### Handle data gaps
Some pools have quiet periods. Here's how to deal with gaps:
```bash theme={null}
# Filter out low-volume periods that might have unreliable prices
curl "https://api.dexpaprika.com/networks/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/ohlcv?start=2025-01-01&limit=30&inversed=true" | jq '.[] | select(.volume > 1000)'
```
### Multi-pool strategy
For major tokens, cross-reference data from multiple pools:
```bash theme={null}
# Get the top USDC pools to compare price consistency
curl "https://api.dexpaprika.com/networks/ethereum/pools/search?token_address=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&limit=5" | jq '.results[] | .id'
```
### Rate limiting
Don't hammer the API. Keyless and free-key requests are capped at 30 a minute, and [Pro lifts that to 300 a minute](https://dexpaprika.com/api/pricing). Batch your requests and cache results:
```javascript theme={null}
// Example: Batch multiple token histories
const tokens = ['USDC', 'WETH', 'USDT'];
const historyPromises = tokens.map(token =>
fetch(`/api/history/${token}`).then(r => r.json())
);
const allHistories = await Promise.all(historyPromises);
```
***
## Troubleshooting common issues
### "Empty response"
Pool might not have data for your date range:
```bash theme={null}
# Check pool age first
curl "https://api.dexpaprika.com/networks/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640" | jq '.created_at'
```
### "Weird price spikes"
You hit a low-liquidity period. Switch to shorter intervals or higher-volume pools:
```bash theme={null}
# Use 6h intervals to smooth out anomalies
curl "https://api.dexpaprika.com/networks/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/ohlcv?start=2025-01-01&interval=6h&limit=50&inversed=true"
```
### "Upside-down prices"
You need the inverted ratio:
```bash theme={null}
# Flip from TOKEN/ETH to ETH/TOKEN
curl "https://api.dexpaprika.com/networks/ethereum/pools/0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640/ohlcv?start=2025-01-01&inversed=true&limit=10"
```
***
## What's next?
Get liquidity, fees, and other pool metrics.
Combine historical data with live prices.
### FAQs
First check the pool’s `created_at`; set `start` to not precede the creation time.
For charts: `1h`/`24h`. For anomaly smoothing: `6h`/`12h`. For near real‑time: `1m`/`5m`.
No trades occurred in that interval; aggregate longer or switch pools with higher activity.
Pass `inversed=true` to flip from token0/token1 to token1/token0.
## Need help?
Ask questions and see what others are building.
Hit a wall? We'll help you debug it.
# Crypto analytics with ClickHouse
Source: https://docs.dexpaprika.com/tutorials/scaling-with-clickhouse
Take your crypto analytics to the next level with ClickHouse. This guide shows you how to build a production-grade data pipeline for massive datasets and lightning-fast queries.
## Tutorial overview
Scale from local to production by ingesting high‑frequency pool OHLCV into ClickHouse for sub‑second analytics.
## From local to production scale
When your analytics needs grow beyond a single machine and you require a database designed for production scale, it's time to consider ClickHouse. ClickHouse is built for handling billions of rows with sub-second query times, making it perfect for production analytics, real-time dashboards, and enterprise-grade data analysis.
Looking for other analytics solutions? Check out our full list of [API Tutorials](/tutorials/tutorial_intro) for more step-by-step guides.
**Why ClickHouse for crypto analytics?**
* **Massive scale:** Built to handle billions of rows and petabytes of data, far beyond the scope of local, in-process databases.
* **Lightning speed:** Optimized columnar storage delivers queries that are 10-100x faster than traditional row-based systems.
* **Real-time ingestion:** Built for continuous data streaming and updates.
* **Production ready:** Used by companies like Uber, Cloudflare, and Spotify for analytics at scale.
```mermaid theme={null}
graph TD;
subgraph "Step 1: Infrastructure";
A["Setup ClickHouse Server"];
end
subgraph "Step 2: Data Ingestion";
B["Run Python ETL script"];
C["DexPaprika API"];
D["ClickHouse database ('crypto_analytics')"];
B --"Fetches pool & OHLCV data"--> C;
B --"Loads millions of rows"--> D;
end
subgraph "Step 3 & 4: Analysis";
E["Option A: Direct SQL queries"];
F["Option B: AI assistant via MCP"];
D --> E;
D --> F;
end
A --> B;
```
**The goal:**
By the end of this guide, you will have a production-grade ClickHouse setup that can:
1. Ingest 15-minute OHLCV data for the top 250 Uniswap v3 pools (7 days of history)
2. Handle real-time data updates via streaming
3. Run complex analytical queries in milliseconds
4. Enable AI-powered analysis through MCP server integration
Install and configure ClickHouse for crypto analytics.
Create a robust data pipeline for high-frequency data ingestion.
Run complex analytics on 15-minute interval data.
Enable AI-powered analysis through MCP server integration.
***
## Step 1: Setting up ClickHouse
### **Option A: Local installation (recommended for learning)**
Install ClickHouse locally for development and testing:
```bash theme={null}
# macOS
brew install clickhouse
```
**macOS specifics: Cask installation**
The `brew install clickhouse` command now installs a Cask, not a standard formula. This provides a single `clickhouse` binary that acts as a multi-tool for both the server and client. Commands that refer to `clickhouse-server` or `brew services` will not work.
Use the following commands instead:
```bash theme={null}
# To start the server on macOS (runs in the foreground):
clickhouse server
# To connect with the client in a new terminal:
clickhouse client
```
```bash theme={null}
# Ubuntu/Debian
sudo apt-get install -y apt-transport-https ca-certificates dirmngr
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 8919F6BD2B48D754
echo "deb https://packages.clickhouse.com/deb stable main" | sudo tee /etc/apt/sources.list.d/clickhouse.list
sudo apt-get update
sudo apt-get install -y clickhouse-server clickhouse-client
# Start the server
sudo systemctl start clickhouse-server
```
### **Option B: ClickHouse Cloud (recommended for production)**
For production workloads, use [ClickHouse Cloud](https://clickhouse.com/cloud):
1. Sign up for a free trial at clickhouse.com/cloud
2. Create a new service
3. Note your connection details (host, port, username, password)
**Moving forward:**
The rest of this tutorial will assume you are using a **local ClickHouse installation (Option A)**. The Python scripts are configured for this by default. If you choose to use ClickHouse Cloud, remember to update the `CLICKHOUSE_HOST`, `CLICKHOUSE_PORT`, `CLICKHOUSE_USER`, and `CLICKHOUSE_PASSWORD` variables in the scripts accordingly.
### **Test your connection**
```bash theme={null}
# Local installation (macOS)
clickhouse client
# Local installation (Linux)
clickhouse-client
# ClickHouse Cloud
clickhouse-client --host your-host.clickhouse.cloud --port 9440 --user default --password your-password --secure
```
***
## Step 2: Build the production ETL pipeline
Create a new file named `build_clickhouse_db.py`. This script efficiently handles high-frequency data from the top 500 pools, incorporating robust error handling and API management strategies. It leverages two key endpoints: the [Top Pools on a DEX endpoint](/api-reference/pools/get-top-x-pools-on-a-networks-dex) to discover pools, and the [Pool OHLCV Data endpoint](/api-reference/pools/get-ohlcv-data-for-a-pool-pair) to fetch historical price data.
```python build_clickhouse_db.py [expandable] theme={null}
import requests
import pandas as pd
import clickhouse_connect
from datetime import datetime, timedelta
import logging
import time
import asyncio
import aiohttp
from typing import List, Dict
import json
import math
# --- Configuration ---
API_BASE_URL = "https://api.dexpaprika.com"
NETWORK = "ethereum"
DEX_ID = "uniswap_v3"
HISTORY_DAYS = 7 # Fetch 7 days of OHLCV data
TOP_POOLS_LIMIT = 250 # Focus on top 250 pools by volume
BATCH_SIZE = 15 # Process pools in smaller batches
CONCURRENT_REQUESTS = 4 # Concurrent requests for API calls
OHLCV_API_LIMIT = 100 # API limit for OHLCV requests
INTERVAL = "15m" # 15-minute intervals
# ClickHouse Configuration
CLICKHOUSE_HOST = "localhost" # or your ClickHouse Cloud host
CLICKHOUSE_PORT = 8123
CLICKHOUSE_USER = "default"
CLICKHOUSE_PASSWORD = "" # Set if using ClickHouse Cloud
CLICKHOUSE_DATABASE = "crypto_analytics"
# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
async def fetch_with_retry(session, url, params=None, retries=5, backoff_factor=0.5):
"""Generic fetch function with exponential backoff."""
for attempt in range(retries):
try:
async with session.get(url, params=params, timeout=30) as response:
response.raise_for_status()
return await response.json()
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
if attempt == retries - 1:
logging.error(f"Final attempt failed for {url}: {e}")
raise
sleep_time = backoff_factor * (2 ** attempt)
logging.warning(f"Request to {url} failed: {e}. Retrying in {sleep_time:.2f}s...")
await asyncio.sleep(sleep_time)
class ClickHouseETL:
def __init__(self):
# Connect without a database first to ensure it exists
with clickhouse_connect.get_client(
host=CLICKHOUSE_HOST,
port=CLICKHOUSE_PORT,
username=CLICKHOUSE_USER,
password=CLICKHOUSE_PASSWORD
) as client:
client.command(f"CREATE DATABASE IF NOT EXISTS {CLICKHOUSE_DATABASE}")
# Now, connect to the specific database for table operations
self.client = clickhouse_connect.get_client(
host=CLICKHOUSE_HOST,
port=CLICKHOUSE_PORT,
username=CLICKHOUSE_USER,
password=CLICKHOUSE_PASSWORD,
database=CLICKHOUSE_DATABASE
)
self.api_semaphore = asyncio.Semaphore(CONCURRENT_REQUESTS)
self.setup_database()
def setup_database(self):
"""Create tables optimized for 15-minute interval data."""
logging.info("Setting up ClickHouse tables...")
# Create pools table with ReplacingMergeTree to handle duplicates
pools_schema = """
CREATE TABLE IF NOT EXISTS pools (
address String,
dex_name String,
volume_24h_usd Float64,
created_at DateTime,
token0_symbol Nullable(String),
token1_symbol Nullable(String),
pair Nullable(String) MATERIALIZED if(isNotNull(token0_symbol) AND isNotNull(token1_symbol), concat(token0_symbol, '-', token1_symbol), NULL),
created_date Date MATERIALIZED toDate(created_at),
volume_rank UInt32
) ENGINE = ReplacingMergeTree(created_at)
ORDER BY (address, volume_24h_usd, created_at)
PARTITION BY toYYYYMM(created_date)
"""
self.client.command(pools_schema)
# Create OHLCV table optimized for time-series analytics
ohlcv_schema = """
CREATE TABLE IF NOT EXISTS pool_ohlcv (
timestamp DateTime,
network String,
pool_address String,
open Float64,
high Float64,
low Float64,
close Float64,
volume_usd Float64,
date Date MATERIALIZED toDate(timestamp),
hour UInt8 MATERIALIZED toHour(timestamp),
minute UInt8 MATERIALIZED toMinute(timestamp),
quarter_hour UInt8 MATERIALIZED intDiv(toMinute(timestamp), 15)
) ENGINE = ReplacingMergeTree(timestamp)
ORDER BY (pool_address, timestamp)
PARTITION BY (date, network)
"""
self.client.command(ohlcv_schema)
logging.info("Database and tables setup complete.")
async def fetch_top_pools(self) -> List[Dict]:
"""Fetch top pools by volume from the specified DEX, handling cursor pagination."""
logging.info(f"Fetching top {TOP_POOLS_LIMIT} pools for {DEX_ID} on {NETWORK}...")
all_pools = []
cursor = None
async with aiohttp.ClientSession() as session:
while len(all_pools) < TOP_POOLS_LIMIT:
# The DEX is a filter now: /networks/{network}/dexes/{dex}/pools was
# removed and returns 410. detailed=true keeps name/symbol on tokens[].
url = f"{API_BASE_URL}/networks/{NETWORK}/pools/search"
params = {"dex_name": DEX_ID, "limit": 100, "order_by": "volume_usd_24h",
"sort": "desc", "detailed": "true"}
if cursor:
params["cursor"] = cursor
try:
data = await fetch_with_retry(session, url, params=params)
pools = data.get('results', [])
if not pools:
break
all_pools.extend(pools)
logging.info(f"Fetched a page, got {len(pools)} pools. Total: {len(all_pools)}")
if len(all_pools) >= TOP_POOLS_LIMIT:
all_pools = all_pools[:TOP_POOLS_LIMIT]
break
if not data.get('has_next_page'):
break
cursor = data.get('next_cursor')
await asyncio.sleep(0.5) # Be respectful to the API
except Exception as e:
logging.error(f"Error fetching a page of pools: {e}")
break
logging.info(f"Finished fetching pools. Total: {len(all_pools)}")
return all_pools
async def fetch_pool_ohlcv_paginated(self, session: aiohttp.ClientSession, pool_address: str) -> List[Dict]:
"""Fetch complete OHLCV data for a pool using intelligent, dynamic windowing."""
async with self.api_semaphore:
final_end_time = datetime.utcnow()
current_start_time = final_end_time - timedelta(days=HISTORY_DAYS)
all_ohlcv = []
try:
interval_minutes = int(INTERVAL.replace('m', ''))
minutes_per_call = OHLCV_API_LIMIT * interval_minutes
time_delta_per_call = timedelta(minutes=minutes_per_call)
except ValueError:
logging.error(f"Invalid INTERVAL format: {INTERVAL}. Defaulting to 15 minutes.")
interval_minutes = 15
time_delta_per_call = timedelta(minutes=OHLCV_API_LIMIT * 15)
while current_start_time < final_end_time:
batch_end_time = min(current_start_time + time_delta_per_call, final_end_time)
url = f"{API_BASE_URL}/networks/{NETWORK}/pools/{pool_address}/ohlcv"
params = {
"start": current_start_time.strftime('%Y-%m-%dT%H:%M:%SZ'),
"end": batch_end_time.strftime('%Y-%m-%dT%H:%M:%SZ'),
"interval": INTERVAL,
"limit": OHLCV_API_LIMIT
}
try:
batch_data = await fetch_with_retry(session, url, params=params)
if batch_data:
for record in batch_data:
record['network'] = NETWORK
record['pool_address'] = pool_address
if 'volume_usd' not in record:
avg_price = (record.get('open', 0) + record.get('close', 0)) / 2
record['volume_usd'] = record.get('volume', 0) * avg_price if avg_price > 0 else 0
all_ohlcv.extend(batch_data)
except Exception as e:
logging.warning(f"Could not fetch OHLCV batch for {pool_address}: {e}")
current_start_time = batch_end_time
await asyncio.sleep(0.75) # Crucial delay to prevent rate-limiting
logging.info(f"Pool {pool_address}: collected {len(all_ohlcv)} OHLCV records.")
return all_ohlcv
async def fetch_pool_ohlcv_batch(self, pool_addresses: List[str]) -> List[Dict]:
"""Fetch OHLCV data for multiple pools concurrently."""
logging.info(f"Fetching {INTERVAL} OHLCV for {len(pool_addresses)} pools...")
all_ohlcv = []
async with aiohttp.ClientSession() as session:
tasks = [self.fetch_pool_ohlcv_paginated(session, addr) for addr in pool_addresses]
results = await asyncio.gather(*tasks, return_exceptions=True)
for i, result in enumerate(results):
if isinstance(result, list):
all_ohlcv.extend(result)
elif isinstance(result, Exception):
logging.warning(f"OHLCV fetch failed for pool {pool_addresses[i]}: {result}")
return all_ohlcv
def load_pools_data(self, pools: List[Dict]):
"""Load pools data into ClickHouse with volume ranking."""
if not pools: return
logging.info("Processing and loading pools data...")
for i, pool in enumerate(pools):
tokens = pool.get('tokens', [])
pool['token0_symbol'] = tokens[0]['symbol'] if len(tokens) > 0 else None
pool['token1_symbol'] = tokens[1]['symbol'] if len(tokens) > 1 else None
pool['volume_rank'] = i + 1
df = pd.DataFrame(pools)
df = df[['id', 'dex_name', 'volume_usd_24h', 'created_at', 'token0_symbol', 'token1_symbol', 'volume_rank']]
df = df.rename(columns={'id': 'address', 'volume_usd_24h': 'volume_24h_usd'})
df['created_at'] = pd.to_datetime(df['created_at'])
self.client.insert_df('pools', df)
logging.info(f"Loaded {len(df)} pools into 'pools' table.")
def load_ohlcv_data(self, ohlcv_data: List[Dict]):
"""Load OHLCV data into ClickHouse."""
if not ohlcv_data: return
logging.info(f"Processing and loading {len(ohlcv_data)} OHLCV records...")
df = pd.DataFrame(ohlcv_data)
df['timestamp'] = pd.to_datetime(df['time_close'])
df = df[['timestamp', 'network', 'pool_address', 'open', 'high', 'low', 'close', 'volume_usd']]
self.client.insert_df('pool_ohlcv', df)
logging.info(f"Loaded {len(df)} records into 'pool_ohlcv' table.")
async def run_etl(self):
"""Run the complete ETL process."""
logging.info(f"Starting ClickHouse ETL process for top {TOP_POOLS_LIMIT} pools...")
pools = await self.fetch_top_pools()
if pools:
self.load_pools_data(pools)
pool_addresses = [pool['id'] for pool in pools if pool.get('id')]
for i in range(0, len(pool_addresses), BATCH_SIZE):
batch_addresses = pool_addresses[i:i + BATCH_SIZE]
batch_num = (i // BATCH_SIZE) + 1
total_batches = (len(pool_addresses) + BATCH_SIZE - 1) // BATCH_SIZE
logging.info(f"Processing OHLCV batch {batch_num}/{total_batches} ({len(batch_addresses)} pools)")
ohlcv_data = await self.fetch_pool_ohlcv_batch(batch_addresses)
self.load_ohlcv_data(ohlcv_data)
if i + BATCH_SIZE < len(pool_addresses):
logging.info(f"--- Finished batch {batch_num}, sleeping for 10 seconds ---")
await asyncio.sleep(10)
logging.info("ETL process completed!")
pool_count = self.client.command("SELECT COUNT() FROM pools")
ohlcv_count = self.client.command("SELECT COUNT() FROM pool_ohlcv")
unique_pools_with_data = self.client.command("SELECT COUNT(DISTINCT pool_address) FROM pool_ohlcv")
avg_records = ohlcv_count / unique_pools_with_data if unique_pools_with_data > 0 else 0
logging.info(f"Final counts - Pools: {pool_count}, OHLCV records: {ohlcv_count:,}")
logging.info(f"Coverage - {unique_pools_with_data} pools with data, avg {avg_records:.1f} records/pool.")
async def main():
etl = ClickHouseETL()
await etl.run_etl()
if __name__ == "__main__":
# pip install clickhouse-connect aiohttp pandas requests
asyncio.run(main())
```
This script is used for performance and reliability, using several good practices common in data pipelines:
* **Asynchronous operations:** By using `asyncio` and `aiohttp`, the script can make many API requests concurrently instead of one by one.
* **Dynamic windowing:** The `fetch_pool_ohlcv_paginated` function calculates how much data to request per API call based on the `OHLCV_API_LIMIT`.
* **Concurrency control & throttling:** An `asyncio.Semaphore`, combined with carefully tuned `BATCH_SIZE` and `asyncio.sleep()` calls, keeps the script under the free tier's 30 requests a minute. [Pro raises that to 300 a minute](https://dexpaprika.com/api/pricing), so a 250-pool backfill completes in a fraction of the time.
* **Resiliency:** The `fetch_with_retry` function automatically retries failed requests with an exponential backoff delay.
### **Required libraries**
```bash theme={null}
pip install clickhouse-connect aiohttp pandas requests
```
***
## Step 3: Lightning-fast analytics (Optional)
Once your database is populated, you can query it directly using any ClickHouse-compatible SQL client or a Python script. While the next step (AI Integration) is recommended for the most powerful analysis, running queries directly is a great way to verify your data.
You can create a file named `query_clickhouse.py` to see how fast ClickHouse can process complex analytical queries on the millions of rows you've ingested.
```python query_clickhouse.py [expandable] theme={null}
import clickhouse_connect
import pandas as pd
import time
client = clickhouse_connect.get_client(
host='localhost', port=8123, database='crypto_analytics'
)
print("=== ClickHouse 15-Minute Analytics Demo ===\n")
# Query 1: Top Pools by Volume with Data Coverage
print("--- Top 10 Pools by Volume (with data coverage) ---")
start_time = time.time()
result1 = client.query_df("""
SELECT
p.pair,
p.address,
max(p.volume_24h_usd) as volume_24h,
min(p.volume_rank) as best_rank,
count(o.timestamp) as ohlcv_records
FROM pools p
LEFT JOIN pool_ohlcv o ON p.address = o.pool_address
GROUP BY p.pair, p.address
ORDER BY volume_24h DESC
LIMIT 10
""")
print(result1)
print(f"Query executed in {time.time() - start_time:.3f} seconds\n")
# Query 2: 15-Minute Volume Patterns Analysis
print("--- Volume Patterns by 15-Minute Intervals ---")
result2 = client.query_df("""
SELECT
hour,
quarter_hour * 15 as minute_of_hour,
COUNT(DISTINCT pool_address) as active_pools,
round(avg(volume_usd), 2) as avg_15min_volume,
round(sum(volume_usd), 2) as total_15min_volume
FROM pool_ohlcv
WHERE volume_usd > 0 AND volume_usd < 1000000000 -- Defensive filter
GROUP BY hour, quarter_hour
ORDER BY total_15min_volume DESC
""")
print(result2.head(10))
# Query 3: High-Frequency Price Action Analysis (Top 5 Pools)
print("\n--- High-Frequency Volatility Analysis (Top 5 Pools) ---")
result3 = client.query_df("""
WITH top_pools AS (
SELECT address from pools ORDER BY volume_24h_usd DESC LIMIT 5
),
pool_volatility AS (
SELECT
o.pool_address,
p.pair,
(o.high - o.low) / o.low * 100 as interval_volatility
FROM pool_ohlcv o
JOIN pools p ON o.pool_address = p.address
WHERE o.pool_address IN (SELECT address FROM top_pools) AND o.low > 0
)
SELECT
pair,
pool_address,
avg(interval_volatility) as avg_15min_volatility,
max(interval_volatility) as max_15min_volatility
FROM pool_volatility
GROUP BY pair, pool_address
ORDER BY avg_15min_volatility DESC
""")
print(result3.head(15))
# Query 4: Peak Trading Hours Analysis
print("\n--- Peak Trading Hours Analysis ---")
result4 = client.query_df("""
SELECT
hour,
COUNT(DISTINCT pool_address) as active_pools,
round(sum(volume_usd), 2) as hourly_volume,
round(avg(volume_usd), 2) as avg_15min_volume,
round(avg((high - low) / low * 100), 4) as avg_volatility_pct
FROM pool_ohlcv
WHERE volume_usd > 0 AND low > 0 AND volume_usd < 1000000000 -- Defensive filter
GROUP BY hour
ORDER BY hourly_volume DESC
""")
print(result4.head(10))
# Query 5: Database performance and storage stats
print("\n--- Database Performance Stats ---")
stats = client.query_df("""
SELECT
table as table_name,
formatReadableSize(sum(bytes_on_disk)) as size,
sum(rows) as row_count,
formatReadableSize(sum(bytes_on_disk)/sum(rows)) as avg_row_size
FROM system.parts
WHERE database = 'crypto_analytics' AND active = 1
GROUP BY table
""")
print(stats)
# Query 6: Data quality check
print("\n--- Data Quality Summary ---")
quality_check = client.query_df("""
SELECT
'Total Records' as metric, toString(COUNT(*)) as value
FROM pool_ohlcv
UNION ALL SELECT
'Date Range', concat(toString(MIN(date)), ' to ', toString(MAX(date)))
FROM pool_ohlcv
UNION ALL SELECT
'Unique Pools with Data', toString(COUNT(DISTINCT pool_address))
FROM pool_ohlcv
UNION ALL SELECT
'Avg Records per Day', toString(ROUND(COUNT(*) / nullif(COUNT(DISTINCT date), 0)))
FROM pool_ohlcv
UNION ALL SELECT
'Expected 15min Intervals vs Actual',
concat(
toString(dateDiff('minute', MIN(timestamp), MAX(timestamp)) / 15),
' vs ',
toString(count())
)
FROM pool_ohlcv
""")
print(quality_check)
```
You can run the script by executing it from your terminal:
```bash theme={null}
python query_clickhouse.py
```
Now, let's move on to the recommended final step: connecting your database to an AI assistant.
***
## Step 4: AI-powered analysis with an MCP server
Enable seamless analysis of your local ClickHouse database through the [ClickHouse MCP Server](https://github.com/ClickHouse/mcp-clickhouse). This allows AI assistants like Claude Desktop to connect to your database, list tables, and run `SELECT` queries securely.
### 1. Install the MCP server
The server is a Python package that can be installed via `pip`:
```bash theme={null}
pip install clickhouse-mcp-server
```
### 2. Configure your AI client
Next, configure your AI client (e.g., Claude Desktop) to use the server. You'll need to edit its configuration file.
* **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows:** `%APPDATA%/Claude/claude_desktop_config.json`
Add the following JSON block to the `mcpServers` section of the file. This tells the client how to run the server and provides the connection details for your local ClickHouse instance.
**Finding the command path**
The most common point of failure is an incorrect command path. The command should be the **absolute path** to the `clickhouse-mcp-server` executable that `pip` installed.
Find this path by running `which clickhouse-mcp-server` in your terminal and use the output in the `command` field below.
```json theme={null}
{
"mcpServers": {
"clickhouse-mcp-server": {
"command": "/path/to/your/clickhouse-mcp-server",
"args": [],
"env": {
"CLICKHOUSE_HOST": "localhost",
"CLICKHOUSE_USER": "default",
"CLICKHOUSE_PASSWORD": "",
"CLICKHOUSE_DATABASE": "crypto_analytics",
"CLICKHOUSE_SECURE": "false"
}
}
}
}
```
### 3. Restart and analyze
Save the configuration file and **restart your AI client**. Once restarted, you can start asking it to analyze the data in your `crypto_analytics` database.
### Troubleshooting & important notes
* **"Server disconnected" error:** This almost always means the `command` path in your configuration is incorrect. Double-check the absolute path using `which clickhouse-mcp-server`.
* **AI connects to the `default` database:** We observed that the AI client might sometimes choose to connect to the `default` database on its own, even if `crypto_analytics` is specified in the config. This will result in it seeing no tables.
* **Solution: Be explicit:** To ensure the AI works correctly, always specify the database in your prompt. This overrides the AI's tendency to use the default.
**Good example prompts:**
* "Using the clickhouse-mcp-server, **connect to the `crypto_analytics` database** and then list the tables."
* "**In the `crypto_analytics` database**, show me the top 10 pools by volume from the `pools` table."
* "Calculate the average daily volume for the top 5 most volatile pools **from the `crypto_analytics` database**."
***
## What you've built: A production-grade analytics pipeline
Congratulations! You've successfully built a scalable crypto analytics pipeline with ClickHouse. You've ingested a large dataset of OHLCV data, and you've enabled a powerful AI assistant to securely query and analyze that data.
**Key achievements:**
* **Built a production-ready ETL pipeline:** You have a reusable, high-performance Python script that can create a comprehensive, multi-million row database from any supported DEX and network.
* **Unlocked lightning-fast SQL:** You can now perform complex analytical queries on a massive dataset in milliseconds, directly on your machine.
* **Mastered a scalable workflow:** This "local-first" data strategy, combined with ClickHouse's power, provides a solid foundation for building real-time dashboards, conducting in-depth market research, and developing sophisticated trading algorithms.
* **Enabled secure AI analysis:** By connecting your database to an AI assistant via an MCP server, you've created a powerful and secure way to explore your data using natural language.
### FAQs
When your dataset no longer fits comfortably on a single machine or you need concurrent users and sub‑second queries on billions of rows.
Tune `BATCH_SIZE` and concurrency based on CPU/network; watch error rates and back off on timeouts.
`15m` is a great compromise; use `1m/5m` for ultra‑short‑term dashboards.
Run the ETL incrementally (cron or scheduler) to append the latest OHLCV windows.
# API tutorials
Source: https://docs.dexpaprika.com/tutorials/tutorial_intro
Learn how to use the DexPaprika API with step-by-step tutorials.
## Available tutorials
| Tutorial | Description |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| [CLI](/tutorials/cli) | Install the DexPaprika CLI and query DEX data from your terminal. |
| [Fetching token prices](/tutorials/fetch-token-price) | Retrieve the price of any token using the DexPaprika API. |
| [Batch token prices](/tutorials/batch-token-prices) | Fetch prices for multiple tokens in a single request, for up to 90% fewer HTTP calls, though the credit cost is unchanged. |
| [Pool filtering](/tutorials/pool-filtering) | Filter pools by volume, transaction count, and creation date using the advanced filter endpoint. |
| [Find new pools](/tutorials/find-new-pools) | Discover newly created liquidity pools on any network by sorting pools by creation date. |
| [Retrieve historical data](/tutorials/retrieve-historical-data) | Get OHLCV (Open, High, Low, Close, Volume) historical price data for any token. |
| [Vibe coding with AI](/ai-integration/vibe-coding-with-ai) | Use Claude Code or Cursor with DexPaprika access to build a crypto dashboard from natural language prompts. |
| [Build a price alert bot](/tutorials/crypto-alert-bot) | Create a real-time cryptocurrency price alert system using DexPaprika API and Telegram. |
| [Local analytics with DuckDB](/tutorials/local-analytics-with-duckdb) | Build a local analytics database with DuckDB to query Uniswap v3 data instantly. |
| [Scaling with ClickHouse](/tutorials/scaling-with-clickhouse) | Create a production-grade ClickHouse pipeline for massive datasets. |
| [Real-time analytics with InfluxDB](/tutorials/real-time-analytics-with-influxdb) | Time-series analytics pipeline for real-time monitoring. |
Do you have an interesting implementation that you want to share with others? We'd be happy to share your tutorial utilizing our API with others. [Reach us out on Discord!](https://discord.gg/DhJge5TUGM)
## Get support
Connect with our community and get real-time support.
Share your experience and help us improve the API.
### FAQs
No. Every example runs keyless. A [free registered key](https://console.dexpaprika.com) raises the monthly credit allowance from 50,000 to 300,000, and none of the examples need it.
Check the [Networks API](/api-reference/networks/get-a-list-of-available-blockchain-networks) for the current list.
Use curl from your terminal or the API Reference playground directly in these docs.