coinpaprika / dexpaprika-sdk
Official PHP SDK for the DexPaprika API. Access multi-chain DEX data, token prices, and liquidity pools with advanced caching and error handling.
Requires
- php: >=7.4
- ext-json: *
- guzzlehttp/guzzle: ^8.0
- psr/log: ^1.1 || ^2.0 || ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- phpunit/phpunit: ^9.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-10-21 20:09:56 UTC
README
A PHP SDK for interacting with the DexPaprika API, providing access to cryptocurrency DEX data, token information, liquidity pools, and market statistics.
The DEX pools endpoint is gone (v1.6.0)
DexPaprika removed GET /networks/{network}/dexes/{dex}/pools. It returns 410 Gone.
DexesApi::getDexPools() now calls /networks/{network}/pools/search with a dex_name
filter. The method name and its arguments are unchanged: the DEX id you pass is sent as
dex_name. Despite that parameter's name it matches the DEX id from
GET /networks/{network}/dexes (the dex_id field), case-insensitively. Passing that
response's dex_name field instead, a human display name such as Uniswap V3, returns
HTTP 200 with an empty result set rather than an error, so always pass the id.
The response is the cursor-paginated search shape, so rows live under results and the
24h volume field is volume_usd_24h. There is no bare volume_usd and no page_info.
// Before $pools = $client->dexes->getDexPools('ethereum', 'uniswap_v3', ['limit' => 15]); foreach ($pools['pools'] as $pool) { echo $pool['volume_usd']; } // After $pools = $client->dexes->getDexPools('ethereum', 'uniswap_v3', ['limit' => 15]); foreach ($pools['results'] as $pool) { echo $pool['id'] . ' ' . $pool['volume_usd_24h']; } // Page with the cursor. Paging past the first page needs an API key (a free // key pages without limit), configured before the client that uses it: // https://console.dexpaprika.com $config = (new Config())->setApiKey(getenv('DEXPAPRIKA_API_KEY')); $client = new Client(null, null, false, $config); $next = $client->dexes->getDexPools('ethereum', 'uniswap_v3', [ 'limit' => 15, 'cursor' => $pools['next_cursor'], ]);
Tokens inside a search row carry id, chain and has_image only. Call
$client->tokens->getTokenDetails($chain, $id) if you need names or symbols.
Breaking changes in v1.3.0
IMPORTANT: The global /pools endpoint has been deprecated in DexPaprika API v1.3.0. All pool operations now require a network parameter.
Migration Required
// OLD - No longer works $pools = $client->pools->getTopPools(['limit' => 10]); // NEW - Network-specific approach $ethereumPools = $client->pools->getNetworkPools('ethereum', ['limit' => 10]); $solanaPools = $client->pools->getNetworkPools('solana', ['limit' => 10]);
See the Migration Guide and CHANGELOG for complete details.
Unified search endpoints (v1.2.0)
getNetworkPools(), filterPools(), getTopTokens() and filterTokens() now call the
unified pools/search and tokens/search endpoints (the previous list/filter/top
endpoints return 410 Gone). The public method signatures are unchanged: legacy sort
values (for example volume_usd, fdv) and legacy filter parameter names (for example
volume24hMin) are mapped to the canonical search names automatically.
These endpoints are cursor-paginated, so the response shape is:
$page = $client->pools->getNetworkPools('ethereum', ['limit' => 20]); $page['results']; // array of items $page['has_next_page']; // bool $page['next_cursor']; // string|null // Fetch the next page with the cursor. Paging past the first page needs an // API key (a free key pages without limit), configured before the client // that uses it: https://console.dexpaprika.com $config = (new Config())->setApiKey(getenv('DEXPAPRIKA_API_KEY')); $client = new Client(null, null, false, $config); $next = $client->pools->getNetworkPools('ethereum', [ 'limit' => 20, 'cursor' => $page['next_cursor'], ]);
The page option is still accepted for backward compatibility but is ignored.
Features
- Simple and intuitive PHP interface to all DexPaprika API endpoints
- Access data from 36 blockchain networks
- Query information about DEXes, liquidity pools, and tokens
- Get detailed price information, trading volume, and transactions
- Filter pools and tokens by volume, liquidity, FDV, transactions, and creation date
- Get top tokens on any network ranked by volume or other metrics
- Batch price lookups for up to 10 tokens in a single request
- Search across the entire DexPaprika ecosystem
- Automatic retry with exponential backoff
- Response caching with PSR-6 compatible interface
- Parameter validation with clear error messages
- Comprehensive documentation
Requirements
- PHP 7.4 or higher
- Composer
ext-json
Installation
Install via Composer:
composer require coinpaprika/dexpaprika-sdk
Basic Usage
<?php require_once 'vendor/autoload.php'; use DexPaprika\Client; use DexPaprika\Exception\DexPaprikaApiException; try { // Create client $client = new Client(); // Get list of supported networks $networks = $client->networks->getNetworks(); // Get global statistics $stats = $client->utils->getStats(); // Get top pools by network (NEW in v1.3.0) $ethereumPools = $client->pools->getNetworkPools('ethereum', ['limit' => 10]); $solanaPools = $client->pools->getNetworkPools('solana', ['limit' => 10]); // Search for tokens $searchResults = $client->search->search('bitcoin'); // Get token details $token = $client->tokens->getTokenDetails('ethereum', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'); } catch (DexPaprikaApiException $e) { echo "API Error: " . $e->getMessage(); }
Using an API key (optional)
The SDK works without a key. No signup, no card. Everything below is optional.
One exception: paging search results past the first page needs a key.
pools/search, tokens/search and the methods backed by them
(getNetworkPools(), getDexPools(), getTokenPools(), filterPools(),
filterTokens(), getTopTokens()) serve the first page keyless, limit and
all. Asking for a second page (passing cursor) without a key gets a 403
from the API. Any key, a free one included, pages without limit. Register at
https://console.dexpaprika.com.
A free key raises the monthly credit allowance. It does not raise the per-minute request limit, which is the same on both free tiers. Current figures are on the rate limits page.
use DexPaprika\Client; use DexPaprika\Config; // Explicit $config = (new Config())->setApiKey('api_your_key_here'); $client = new Client(null, null, false, $config); // Or leave it out and set DEXPAPRIKA_API_KEY in the environment $client = new Client();
setApiKey() beats the environment variable, and no key at all keeps the
previous keyless behaviour unchanged.
The key is sent as the entire Authorization value, with nothing in front of
it. You never write the header yourself, so this only matters when debugging
what went out.
Dev and Pro customers also set the base URL, because the host does not change on its own. Free keys are served from the default host and sending one to the Pro host returns 403, so the switch has to be deliberate:
$config = (new Config()) ->setApiKey('api_your_pro_key') ->setBaseUrl('https://api-pro.dexpaprika.com');
A malformed key on api-pro.dexpaprika.com never reaches the API at all. It
gets a 403 straight from the edge in front of it, an HTML page, not the API's
usual JSON error. The SDK turns that into a ClientException whose message says
the key was refused before it reached the API: check that the whole key was pasted
into the Authorization header with nothing in front of it, and that it is a key
from https://console.dexpaprika.com. A 403 with a JSON body, such as the
keyless-paging error above, is unaffected and keeps its own message.
One gotcha worth knowing. On the data endpoints a key the API cannot read is
ignored rather than rejected: the call returns 200 with real data and you are
quietly served as an anonymous caller. To confirm a key is landing, call /usage
and check the plan field; keyless means the key never arrived.
Advanced Configuration
<?php use DexPaprika\Client; use DexPaprika\Config; // Create configuration $config = new Config(); $config->setResponseFormat('object') // Get responses as objects instead of arrays ->setTimeout(30) // Set request timeout in seconds ->setUserAgent('MyApp/1.0') // Set custom user agent ->setMaxRetries(3) // Set maximum retry attempts ->setRetryDelays([100, 500, 1000]); // Set retry delays in milliseconds // Create client with configuration $client = new Client($config);
Caching Responses
The SDK provides built-in caching for API responses:
<?php use DexPaprika\Client; use DexPaprika\Cache\FilesystemCache; // Enable caching with default settings (filesystem cache, 1 hour TTL) $client = new Client(); $client->setupCache(); // Custom cache TTL (5 minutes) $client->setupCache(null, 300); // Custom cache directory $customCache = new FilesystemCache('/path/to/cache'); $client->setupCache($customCache); // Disable caching $client->getConfig()->setCacheEnabled(false); // Enable caching $client->getConfig()->setCacheEnabled(true); // Create a client with caching enabled from the beginning $config = new Config(); $cache = new FilesystemCache(); $config->setCache($cache)->setCacheEnabled(true); $client = new Client(null, null, false, $config);
Automatic Retry
The SDK automatically retries failed requests with exponential backoff:
<?php use DexPaprika\Client; use DexPaprika\Config; // Configure retry behavior $config = new Config(); $config->setMaxRetries(5); // Maximum number of retry attempts $config->setRetryDelays([100, 500, 1000, 2500, 5000]); // Delay in milliseconds for each attempt $client = new Client(null, null, false, $config);
Only certain types of failures will be retried:
- Network connectivity issues
- Server errors (5xx status codes)
- Rate limiting (429 status codes)
A 429 waits for what the API actually asked for, not just the fixed
schedule above. The API sends the wait as a Retry-After header on a keyed
request, or a retry_after field in the JSON body on a keyless one (which
carries no header at all). The SDK checks the header first, then the body,
and if the server's figure is longer than the next scheduled delay, the
server's figure is used instead, clamped to 60 seconds. An HTTP-date form of
the header is not parsed; that case, like a response with neither field, uses
the schedule above.
Sleeping between retries goes through a function you can replace:
$config = new Config(); $sleeps = []; $config->setSleeper(function (int $milliseconds) use (&$sleeps) { $sleeps[] = $milliseconds; // record instead of actually waiting });
Tests use this to exercise retry behavior, including a 60 second wait, without a 60 second test. Leave it unset and the SDK sleeps for real.
API Components
This SDK provides the following API modules:
Networks
// Get all supported networks $networks = $client->networks->getNetworks();
Statistics
// Get global DEX statistics $stats = $client->utils->getStats();
DEXes
// Get all DEXes on a specific network $dexes = $client->dexes->getNetworkDexes('ethereum', ['limit' => 10]);
Pools
// Get pools on a specific network (UPDATED in v1.3.0) $ethPools = $client->pools->getNetworkPools('ethereum', ['limit' => 20]); $solanaPools = $client->pools->getNetworkPools('solana', ['limit' => 20]); // Get pools on a specific DEX (cursor-paginated, rows under 'results') $uniswapPools = $client->dexes->getDexPools('ethereum', 'uniswap_v3', ['limit' => 15]); $uniswapPools['results'][0]['volume_usd_24h']; // Get detailed information about a pool $poolDetails = $client->pools->getPoolDetails('ethereum', '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640'); // Hourly OHLCV candles for the last 24 hours, which works without a key $ohlcvData = $client->pools->getPoolOHLCV('ethereum', '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640', '-24h', [ 'interval' => '1h', 'limit' => 24 ] ); // Get transactions for a pool $transactions = $client->pools->getPoolTransactions('ethereum', '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640', ['limit' => 20]); // Only the last hour of transactions $lastHour = $client->pools->getPoolTransactions('ethereum', '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640', ['limit' => 20, 'from' => '-1h']);
OHLCV start and end take a relative offset from now (-24h, -7d, -90m) as well as ISO dates and Unix timestamps. How far back you can go and how fine the candles can be depends on your plan: without a key, the last 24 hours at 1h and longer; a free key opens 7 days at 10m and longer; Dev 30 days at every interval; Pro unlimited. A request outside your plan throws a ClientException carrying the API's message. Full table: OHLCV limits by plan.
Transaction from and to, and createdAfter and createdBefore on filterPools and filterTokens, take the same relative offsets, as well as Unix seconds, RFC 3339 and YYYY-MM-DD. Transactions go back 7 days at most.
Tokens
// Get detailed information about a token $tokenDetails = $client->tokens->getTokenDetails('ethereum', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'); // Find token by name or address $token = $client->tokens->findToken('ethereum', 'WETH'); // Get pools containing a specific token (UPDATED in v1.5.0). // Backed by /networks/{network}/pools/search with token_address: the filter is // network-scoped, rows come back under 'results', and pagination is // cursor-based ('cursor' option / 'next_cursor' in the response). $tokenPools = $client->tokens->getTokenPools('ethereum', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', [ 'limit' => 20, ]); foreach ($tokenPools['results'] as $pool) { echo $pool['id'] . ': ' . $pool['volume_usd_24h'] . "\n"; } // The removed endpoint's 'address' (pair filter) and 'reorder' options have no // /pools/search equivalent and are ignored. To match a pair, filter client-side: $usdc = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; $pairPools = array_filter($tokenPools['results'], function ($pool) use ($usdc) { return in_array($usdc, array_column($pool['tokens'] ?? [], 'id'), true); });
Token OHLCV
Candles for a token, priced in USD: a volume-weighted price built across every
pool the token trades in on the network, with volume summed the same way. Same
record shape as pool OHLCV, and no inversed option, because a USD price has
no pair side to flip.
This endpoint needs a Dev, Pro or Enterprise plan and must be called against
api-pro.dexpaprika.com with the API key set as the whole Authorization
value:
$config = (new Config()) ->setApiKey('api_your_dev_or_pro_key') ->setBaseUrl('https://api-pro.dexpaprika.com'); $client = new Client(null, null, false, $config); $tokenOhlcv = $client->tokens->getTokenOHLCV( 'ethereum', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', '-24h', [ 'interval' => '1h', 'limit' => 24, ] );
A keyless or free-key call gets 403, and getTokenOHLCV() throws a
ClientException carrying the API's message. See
Get OHLCV data for a token
and current plan details on the
pricing page.
Technical indicators
getPoolIndicator() and getTokenIndicator() compute rsi, ema, macd,
bbands or vwap over a pool's or token's OHLCV candles. Only the parameters
the chosen indicator uses should be set: period for rsi, ema, bbands and
vwap; fast_period, slow_period and signal_period for macd; std_dev for
bbands. Sending a parameter that belongs to a different indicator is refused
by the API with a 400.
Pool indicators work on every plan, with the same interval and history limits as pool OHLCV above, so this RSI call works without a key:
$rsi = $client->pools->getPoolIndicator('ethereum', '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640', 'rsi', [ 'interval' => '1h', 'start' => '-6h', ] ); foreach ($rsi['data'] as $point) { echo $point['timestamp'] . ': ' . $point['value'] . "\n"; } // MACD on the same pool: 'data' rows carry macd, signal and histogram instead of value $macd = $client->pools->getPoolIndicator('ethereum', '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640', 'macd', [ 'interval' => '1h', 'start' => '-6h', ] );
Token indicators need a Dev, Pro or Enterprise key and must be called against
api-pro.dexpaprika.com, the same host as getTokenOHLCV():
$tokenRsi = $client->tokens->getTokenIndicator('ethereum', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', 'rsi', [ 'interval' => '1h', 'start' => '-6h', ] );
A keyless or free-key call to getTokenIndicator() gets 403, and the thrown
ClientException carries the API's message; getErrorData()['required_tier']
names the plan that lifts the limit. See
Get a technical indicator for a pool,
Get a technical indicator for a token,
the technical indicators tutorial,
current rate limits on the
rate limits page,
plan pricing on the pricing page, and
get a free key at console.dexpaprika.com.
Pool Filtering
// Find high-volume pools on Ethereum $filtered = $client->pools->filterPools('ethereum', [ 'volume24hMin' => 100000, 'txns24hMin' => 50, 'sortBy' => 'volume_24h', 'sortDir' => 'desc', 'limit' => 10, ]); echo "Found " . count($filtered['results']) . " pools matching criteria\n";
Price change windows
pools/search sorts and filters on five price-change windows: 48h, 24h, 6h, 1h and 5m.
Pass the window as sortBy, or bound it with the matching min/max option. Bounds are
percentages, and a max is normally negative because that is how you ask for a drop.
// Pools that fell 10% or more in the last hour, worst first $dumping = $client->pools->filterPools('ethereum', [ 'priceChangePercentage1hMax' => -10, 'liquidityUsdMin' => 10000, 'sortBy' => 'price_change_percentage_1h', 'sortDir' => 'asc', 'limit' => 10, ]); // Fastest movers over the last five minutes $movers = $client->pools->getNetworkPools('ethereum', [ 'orderBy' => 'price_change_percentage_5m', 'sort' => 'desc', 'limit' => 10, ]); // Pools up 100% or more over two days $doubled = $client->pools->filterPools('ethereum', [ 'priceChangePercentage48hMin' => 100, 'sortBy' => 'price_change_percentage_48h', 'sortDir' => 'desc', 'limit' => 10, ]);
Tighten either bound and the result set can legitimately come back empty, because a
screen for a sharp hourly drop on a well funded pool matches nothing on a calm day.
An empty results[] there is an answer, not a failure.
The 24h window is the only one of the five that also works on tokens. getTopTokens()
sorts by price_change_percentage_24h, and filterTokens() takes the matching bounds:
// Tokens down 20% or more on the day $dropped = $client->tokens->filterTokens('ethereum', [ 'priceChangePercentage24hMax' => -20, 'limit' => 10, ]);
The 48h, 6h, 1h and 5m windows exist on pools only. tokens/search returns 400 when you
sort by one of them, silently ignores their filter bounds, and token rows carry no
short-window price-change field at all.
Top Tokens & Token Filtering
// Get top tokens by volume $topTokens = $client->tokens->getTopTokens('ethereum', [ 'orderBy' => 'volume_24h', 'limit' => 10, ]); // Filter tokens by criteria $filtered = $client->tokens->filterTokens('ethereum', [ 'volume24hMin' => 100000, 'fdvMin' => 1000000, 'limit' => 10, ]);
Batch Token Prices
// Get prices for multiple tokens in one request (max 10) $prices = $client->tokens->getMultiPrices('ethereum', [ '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // WETH '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC ]); foreach ($prices as $p) { echo "{$p['id']}: \${$p['price_usd']}\n"; }
Search
// Search for tokens, pools, and DEXes $searchResults = $client->search->search('bitcoin');
Response Formats
By default, the SDK returns responses as PHP arrays. You can change this to objects:
// When initializing the client $config = new Config(); $config->setResponseFormat('object'); $client = new Client($config); // After initialization $client->getConfig()->setResponseFormat('object'); // Get results as objects $networks = $client->networks->getNetworks(); echo $networks[0]->display_name;
Exception Handling
The SDK throws different types of exceptions:
use DexPaprika\Exception\DexPaprikaApiException; use DexPaprika\Exception\NotFoundException; use DexPaprika\Exception\DeprecationException; use DexPaprika\Exception\ValidationException; use DexPaprika\Exception\RateLimitException; use DexPaprika\Exception\ServerException; use DexPaprika\Exception\NetworkException; try { // This will throw DeprecationException in v1.3.0+ $pools = $client->pools->getTopPools(); } catch (DeprecationException $e) { // Handle deprecated endpoint usage echo "Deprecated endpoint: " . $e->getMessage(); // Get migration guidance from error data $errorData = $e->getErrorData(); if (isset($errorData['migration_examples'])) { echo "Migration examples:\n"; foreach ($errorData['migration_examples'] as $example) { echo " {$example}\n"; } } } catch (ValidationException $e) { // Handle parameter validation errors echo "Invalid parameters: " . $e->getMessage(); } catch (NotFoundException $e) { // Handle not found error echo "Resource not found: " . $e->getMessage(); } catch (RateLimitException $e) { // Handle rate limiting echo "Rate limit exceeded. Try again later."; } catch (ServerException $e) { // Handle server errors echo "Server error: " . $e->getMessage(); } catch (NetworkException $e) { // Handle network connectivity issues echo "Network error: " . $e->getMessage(); } catch (DexPaprikaApiException $e) { // Handle API errors echo "API Error: " . $e->getMessage() . " (Code: " . $e->getCode() . ")"; // Get raw error response $errorData = $e->getErrorData(); } catch (Exception $e) { // Handle general errors echo "Error: " . $e->getMessage(); }
Examples
See the examples directory for more comprehensive usage examples:
basic_usage.php- Simple examples of the core functionalityadvanced_usage.php- Advanced usage including pagination, error handling, and more
Testing
Run tests with PHPUnit:
# Run unit tests only ./vendor/bin/phpunit --testsuite Unit # Run all tests including integration (requires API access) ./vendor/bin/phpunit
License
This project is licensed under the MIT License. See the LICENSE file for details.
Contributing
Contributions, issues, and feature requests are welcome! Feel free to check the issues page or submit a pull request.
Versioning
This project adheres to Semantic Versioning. For the list of available versions, see the tags on this repository.
Support
For support, please open an issue on the GitHub repository or contact the DexPaprika team.
Resources
- Official Documentation - Comprehensive API reference
- DexPaprika Website - Main product website
- CoinPaprika - Related cryptocurrency data platform