# DexScreener Pair Price & Liquidity Scraper (`dami_studio/dexscreener-pairs-scraper`) Actor

One row per DEX trading pair: price in USD and in the quote token, pool liquidity, 24h volume, price change and trade counts. Every row carries the timestamp the source itself put on the figures.

- **URL**: https://apify.com/dami\_studio/dexscreener-pairs-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Business, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 pair scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## DexScreener Pair Price & Liquidity Scraper

Search by symbol, paste a token address or paste a pool address, and get one row per trading pair: price in USD and in the quote token, how much liquidity the pool holds, 24-hour volume, price change over five minutes to 24 hours, and the buy and sell counts. Public market data, read straight from a public API. No wallet, no API key, no browser.

- Every row carries `snapshotAt` and `sourceAgeSeconds`: when the source produced the figures, and how old they already were when the run read them. Market data with no timestamp on it is not worth much.
- Ask for a specific pool and the address that comes back is checked against the address you asked for. A mismatch is dropped and not charged.
- A query that matches nothing is a normal answer, not an error. You get a row saying so, free, and the run still finishes clean.
- Token addresses are read in batches of thirty, so a hundred tokens is four requests rather than a hundred.
- One run can cover every chain the source indexes. Solana, Ethereum, Base, BSC, Arbitrum, all of them together.

### Price

**$1.40 per 1,000 pairs**, plus a **$0.0007 start fee per run**.

This is a flat rate on every plan, free or paid. There are no volume tiers, no minimum spend, no subscription and no add-on fees. What you read here is what you pay on day one and on day four hundred.

| Pairs | Total cost |
|---|---|
| 30 | $0.0427 |
| 100 | $0.1407 |
| 500 | $0.7007 |
| 3,000 | $4.2007 |

One run can return up to 3,000 pairs, so every line in that table is a single run and a single start fee.

#### What is actually charged

- **One `pair-scraped` event per pair row written to the dataset.** Nothing else is metered per row.
- **Free:** the sample row an empty run returns, and every diagnostic row: a blocked target, a dead URL, a search that matched nothing. Those rows all carry `"charged": false`.
- Pools you filtered out with `minLiquidityUsd` are dropped before they are written, so you are not billed for dust.
- A pair that shows up twice in one run, when two search terms find the same pool, is written and charged once.
- A run that finds nothing costs the start fee and nothing more.
- Rows never leave the dataset without a charge, and are never charged without a row.

### Input

```json
{
  "searchTerms": [
    "SOL/USDC",
    "PEPE"
  ],
  "tokenAddresses": [
    "solana:So11111111111111111111111111111111111111112"
  ],
  "pairAddresses": [
    "base:0x1131DB5977242a03eBeaD1aCD18F80A9A29e5922"
  ],
  "minLiquidityUsd": 10000,
  "maxItems": 50
}
```

| Field | What it does |
|---|---|
| `searchTerms` | Free text: a symbol, a name, a pair like `SOL/USDC`, or a contract address. At most 30 pairs come back per term and there is no second page, so a broad symbol gives you the top of the list. Up to 100 terms. |
| `tokenAddresses` | Token contracts as `chain:address`, or a pasted dexscreener.com URL. One row per token by default: the pool holding the most liquidity. Up to 100. |
| `pairAddresses` | Pool addresses as `chain:address`, or a pasted pair URL. One row each, and the answer is checked against what you asked for. Up to 100. |
| `chainId` | Only needed when you paste bare addresses with no chain in front of them. Anything written as `chain:address` ignores it. |
| `allPoolsPerToken` | Off by default. Turn it on to get every pool listed for each token instead of just the deepest one. Useful for comparing the same token across venues, and it multiplies what you pay by the number of pools. |
| `minLiquidityUsd` | Pools holding less than this are skipped before they are written. A popular symbol returns a lot of near-empty pools; `10000` is a reasonable floor if you only want real markets. |
| `maxItems` | Total rows across everything you asked for. Default 50, hard ceiling 3,000. Keep it low while testing, because you pay per row. |
| `proxyUrls` | Leave empty. Fill it in only if you want the traffic to leave through proxy servers you already pay for, as `http://user:pass@host:port`. |

Run it with **empty input** and you get one clearly labelled sample row, free, so you can see the output shape before you spend anything.

### Output

One row per pair. A real row from a real run:

```json
{
  "ok": true,
  "charged": true,
  "recordType": "pair",
  "query": "SOL/USDC",
  "chainId": "base",
  "dexId": "aerodrome",
  "baseSymbol": "SOL",
  "quoteSymbol": "USDC",
  "priceUsd": 108.62,
  "liquidityUsd": 626266.5,
  "volumeH24": 5661030.39,
  "priceChangeH24": -2.98,
  "marketCapUsd": 25578137,
  "snapshotAt": "2026-09-20T08:14:11.000Z",
  "sourceAgeSeconds": 54,
  "pairUrl": "https://dexscreener.com/base/0x1131db5977242a03ebead1acd18f80a9a29e5922",
  "pairAddress": "0x1131DB5977242a03eBeaD1aCD18F80A9A29e5922",
  "baseAddress": "0x311935Cd80B76769bF2ecC9D8Ab7635b2139cf82",
  "quoteAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "priceNative": 108.6201,
  "liquidityBase": 2717.3217,
  "liquidityQuote": 331110,
  "fdvUsd": 25578137,
  "volumeH6": 1899577.03,
  "volumeH1": 253048.23,
  "volumeM5": 18637.38,
  "priceChangeH6": -1.62,
  "priceChangeH1": -0.1,
  "priceChangeM5": 0.12,
  "buysH24": 3688,
  "sellsH24": 3785,
  "labels": [],
  "pairCreatedAt": "2024-08-21T17:22:29.000Z",
  "retrievedAt": "2026-09-20T08:15:05.125Z"
}
```

#### Field notes

- `snapshotAt`: when the source produced these figures, worked out from what it told us about its own cache. This is the timestamp to trust, not the one on your clock.
- `sourceAgeSeconds`: how many seconds old the figures already were when the run read them. It sits between 0 and about 60. If that matters for what you are doing, read this field before you use the price.
- `retrievedAt`: when the run read it. Always later than `snapshotAt`, and it is the less useful of the two.
- `priceUsd`: the source publishes this as text and it is parsed to a number here. Sub-cent tokens come back in full precision, so expect values like `0.00000412`.
- `priceNative`: price in units of the quote token. For a SOL/USDC pool that is the price in USDC.
- `liquidityUsd`: total value sitting in the pool. It is the field that tells you whether a price is real or whether ten dollars of trade would move it.
- `marketCapUsd`: the source's own figure, based on circulating supply where it knows it. It is often equal to `fdvUsd`, and it is null when the source has no supply figure.
- `labels`: venue-specific tags the source attaches, like a fee tier or a pool version. Empty for most pairs.
- `pairCreatedAt`: when the pool was deployed. Missing on older pairs the source never recorded a creation time for.
- `query`: the term or address that produced this row, so you can join the output back to your input.

Every real row carries `"charged": true`. Sample rows carry `"_sample": true` and diagnostic rows carry `"_diagnostic": true` with an `errorCode` you can filter on, and neither is ever billed.

### How it works

- It reads the public market-data API the site publishes, using the documented search, token and pair routes. There is no page rendering, no login and no key.
- The API answers from a cache with a one-minute lifetime and says how old each answer is. That number is carried onto every row rather than thrown away, so you always know how fresh a price is.
- Requests are paced under the published rate limit, and a throttle is retried automatically rather than by hammering the same endpoint.
- Token addresses are grouped by chain and sent thirty at a time, which is the batch size the API documents.
- Every call has an explicit timeout. A server hiccup is retried with a growing, jittered pause; a bad chain or a dead address is not retried at all, because no amount of asking again will change the answer.

### What people use it for

- Watching the pools for one token across several chains and venues at once, to see where it actually trades and where the price differs.
- Keeping a spreadsheet or database of current prices and pool depth for a basket of tokens, refreshed on a schedule.
- Checking whether a token that looks liquid on one chart has any real depth behind it. `liquidityUsd` and `volumeH24` next to each other answer that in one glance.
- Feeding a dashboard or a bot with a flat, typed table instead of parsing a rendered page.
- Research on new pools: `pairCreatedAt`, first volume and starting liquidity, pulled for a list of addresses in one run.

### What this is not

This returns market data. It does not tell you what to buy, hold or sell, it makes no prediction, and it scores nothing. There is no signal, no rating and no recommendation anywhere in the output. Just the numbers the source published, and the time it published them.

It is also not a live feed. The source answers from a cache that lives for about a minute, so a price can already be up to sixty seconds old when it reaches you. That is why every row carries `sourceAgeSeconds`. If your use needs sub-second prices, this is the wrong tool and you want a venue websocket instead.

### Reading the output

Every run writes three kinds of row, and they are easy to tell apart:

- **Real rows** carry `"charged": true` and `"recordType": "pair"`. One billed event each.
- **The sample row** carries `"_sample": true` and `"charged": false`. There is exactly one, it only appears when you ran with nothing filled in, and it exists so you can look at the shape before spending anything.
- **Diagnostic rows** carry `"_diagnostic": true`, `"charged": false` and an `errorCode` you can switch on.

| `errorCode` | What happened |
|---|---|
| `NO_RESULTS` | The API answered and matched nothing. A real answer, not a failure. |
| `NOT_FOUND` | No pool exists at that address on that chain. |
| `WRONG_TARGET` | The API returned something other than what was asked for, so the row was dropped. |
| `RESULT_TRUNCATED` | The API returned its maximum of 30 and there are more it will not hand over. Your answer is the top of the list, not all of it. |
| `BAD_INPUT` | The chain or address was not one the API recognises. |
| `RATE_LIMITED` | The API throttled the run. Split the work across two runs. |
| `TIME_BUDGET` | The run ran out of time before reaching that target. |
| `NOTHING_MATCHED` | 25 targets in a row came back empty with no rows anywhere, so the run stopped rather than reading the rest. Almost always the wrong chain. It says how many targets went unread. |

If you only want the data, filter on `charged == true`. The count of those rows always equals the number of events you were billed for, so the dataset is its own invoice.

### Address formats it accepts

All four of these mean the same thing, so paste whichever you already have:

```
solana:So11111111111111111111111111111111111111112
solana/So11111111111111111111111111111111111111112
https://dexscreener.com/solana/So11111111111111111111111111111111111111112
So11111111111111111111111111111111111111112        (with chainId set to "solana")
```

Chain names are the source's own: `solana`, `ethereum`, `base`, `bsc`, `arbitrum`, `polygon`, `avalanche`, `pulsechain` and so on. Get the chain wrong and you get a `BAD_INPUT` or `NOT_FOUND` row, free. The same address usually exists on several chains, so this is the easiest thing to get wrong.

### Limitations

- Prices come from a cache with a one-minute life. `sourceAgeSeconds` tells you how old each one is; it is normally under a minute and it is never zero-latency. This is not a tick feed.
- Any single request returns at most 30 pairs and there is no pagination. `limit`, `page` and `offset` are all ignored by the API. A search for a popular symbol gives you the top 30 and a `RESULT_TRUNCATED` row telling you so.
- Search is fuzzy by design. A term can return pairs you did not have in mind, so every row carries the `query` that produced it. Only the token and pair routes check the answer against the request.
- The API allows roughly 300 requests a minute per address and the limit is shared across all of its routes. The run paces itself under that, but a very large job is better split across runs.
- No historical data. There are no candles, no OHLC and no time series. Only the current snapshot, plus the change percentages the source publishes for 5 minutes, 1 hour, 6 hours and 24 hours.
- No holder lists, no wallet activity, no individual trades and no top-trader tables.
- Coverage is whatever the source lists. A brand-new pool can take a few minutes to appear, and a chain the source does not index is not reachable here.
- `marketCapUsd`, `fdvUsd` and `pairCreatedAt` are null wherever the source has no figure. Nothing is estimated to fill a gap.
- Token names and symbols are written by whoever deployed the contract. Two different tokens can share a symbol, which is exactly why the address fields are in every row.
- If 25 targets in a row come back empty and the run has produced no rows at all, it stops and says so instead of reading the remaining ones. That is nearly always the wrong chain on a list of addresses, and the run tells you how many it skipped.
- Hard ceilings: 3,000 rows and 100 targets per run. For more than that, split the work.

### Questions

**How fresh is the price?**

Between zero and about sixty seconds old. The source serves from a one-minute cache and tells us how long each answer has been sitting there, and that number lands on the row as `sourceAgeSeconds`, with `snapshotAt` giving the moment the figures were actually true. Nothing here is dressed up as a live tick.

**What happens if my search matches nothing?**

You get one free row with `errorCode: "NO_RESULTS"` and the run carries on to your other targets. Finding nothing is a real answer and it is never charged. It is deliberately a different code from `NOT_FOUND` and `BAD_INPUT`, so you can tell "there is nothing there" apart from "I could not look".

**Can I get every pool for a token instead of just the biggest?**

Yes. Turn on `allPoolsPerToken`. You will get up to 30 pools per token instead of one, and you pay for each of them, so it is off by default.

**Which chains work?**

All of the ones the source indexes, in the same run. Write the chain in front of the address and you can mix Solana, Ethereum, Base and anything else in a single input list.

**Is this financial advice?**

No. It returns published market data and nothing else. No score, no rating, no recommendation, no prediction. What you do with the numbers is yours.

**Will the run fail if something goes wrong?**

No. A throttle, a dead address or a chain the API does not know produces a free row explaining what happened, and the run still finishes as succeeded. A failed run would still bill you the start fee, which would mean paying to be told something went wrong.

**Can I run this on a schedule?**

Yes. Nothing is held between runs, so the same input on a schedule is safe. Join on `pairAddress` to diff one run against the last.

**How do I get exactly the rows I paid for?**

Filter the dataset on `"charged": true`. Sample and diagnostic rows are always `false`, and the number of charged rows always equals the number of billed events.

# Actor input Schema

## `searchTerms` (type: `array`):

Free-text search: a token symbol, a token name, a pair like SOL/USDC, or a contract address. Each term returns at most 30 pairs and there is no page two, so a broad symbol gives you the top of the list rather than every pair. Up to 100 terms per run.

## `tokenAddresses` (type: `array`):

Token contract addresses, written as chain:address - for example solana:So11111111111111111111111111111111111111112 or ethereum:0x6982508145454ce325ddbe47a25d4ec3d2311933. A dexscreener.com URL pasted straight from the browser works too. By default you get the deepest pool for each token, one row each. Up to 100 per run.

## `pairAddresses` (type: `array`):

Exact pool addresses, written as chain:address, or a pasted dexscreener.com pair URL. One row each. The address that comes back is checked against the one you asked for, so you never get billed for somebody else's pool. Up to 100 per run.

## `chainId` (type: `string`):

Only needed if you paste addresses without a chain prefix. Set it to the chain they belong to - solana, ethereum, base, bsc, arbitrum, polygon and so on. An address written as chain:address ignores this.

## `allPoolsPerToken` (type: `boolean`):

Off by default, which gives you one row per token - the pool with the most liquidity. Turn it on to get every pool the API lists for that token, up to 30 each. Useful for spotting price gaps between venues, and it multiplies what you pay by the number of pools.

## `minLiquidityUsd` (type: `integer`):

Leave at 0 to keep everything. Set it to, say, 10000 and pools holding less than that are dropped before they are written, so you are not billed for dust. A popular symbol returns a lot of near-empty pools.

## `maxItems` (type: `integer`):

Total rows across everything you asked for. Keep it low while you are testing - you pay per row. Hard ceiling 3000.

## `proxyUrls` (type: `array`):

Leave this empty for a normal run. Fill it in only if you want the traffic to leave through proxy servers you already pay for, one URL per line, in the form http://user:pass@host:port.

## Actor input object example

```json
{
  "searchTerms": [
    "SOL/USDC"
  ],
  "chainId": "solana",
  "maxItems": 50
}
```

# Actor output Schema

## `results` (type: `string`):

Every row in the default dataset: query, chainId, dexId, baseSymbol, quoteSymbol, priceUsd, liquidityUsd, volumeH24, priceChangeH24, marketCapUsd, snapshotAt, sourceAgeSeconds, pairUrl, pairAddress, baseAddress, quoteAddress, priceNative, liquidityBase, liquidityQuote, fdvUsd, volumeH6, volumeH1, volumeM5, priceChangeH6, priceChangeH1, priceChangeM5, buysH24, sellsH24, labels, pairCreatedAt, retrievedAt. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchTerms": [
        "SOL/USDC"
    ],
    "chainId": "solana",
    "minLiquidityUsd": 0,
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/dexscreener-pairs-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchTerms": ["SOL/USDC"],
    "chainId": "solana",
    "minLiquidityUsd": 0,
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/dexscreener-pairs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchTerms": [
    "SOL/USDC"
  ],
  "chainId": "solana",
  "minLiquidityUsd": 0,
  "maxItems": 50
}' |
apify call dami_studio/dexscreener-pairs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/dexscreener-pairs-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Q7OAL0GbENbVFcGgH/builds/muJolqTkzfVDPcGtY/openapi.json
