The Solana Tracker solana token price api gives you three REST views of a token's price: getPrice for one snapshot, getMultiplePrices for up to 100 mints in one call, and getTokenInfo for the price and liquidity of every pool the token trades in. Use them for watchlists, portfolio marks and token pages. When the number has to move on screen, use the price WebSocket. When you are about to trade, ask Raptor for a quote.
A price is an observation of a market, not a promise about execution. Most production bugs in this area come from treating it as one, so the rest of this guide covers which call to make, what comes back, and how the numbers mislead you.
Which Solana token price API call to use
Every call below runs against the Solana Data API with an x-api-key header. The SDK method names are from @solana-tracker/data-api 0.5.0.
| Need | SDK method | Endpoint | Returns |
|---|---|---|---|
| Current price of one token | getPrice(mint, priceChanges?) | GET /price | One snapshot: price, liquidity, market cap, lastUpdated |
| Prices for a watchlist | getMultiplePrices(mints, priceChanges?) | GET /price/multi | Object keyed by mint, at most 100 mints per call |
| Price and liquidity per pool | getTokenInfo(mint) | GET /tokens/{token} | Token metadata plus a pools array |
| Coarse history points | getPriceHistory(mint) | GET /price/history | current plus 3d, 5d, 7d, 14d, 30d values |
| Price at a moment | getPriceAtTimestamp(mint, unixSeconds) | GET /price/history/timestamp | Price and the pool it came from |
| Low and high in a window | getPriceRange(mint, from, to) | GET /price/history/range | Lowest and highest price with times |
| Candles for a chart | getChartData(params) | GET /chart/{token} | OHLCV series (see the OHLCV guide) |
postPrice and postMultiplePrices are POST variants of the same lookups. Use them when a long list of mints would make the query string unwieldy. The 100-mint limit still applies: the SDK throws a ValidationError before sending a larger batch.
Always identify tokens by mint address. Symbols are not unique, and anyone can launch a token called USDC.
REST snapshot, price WebSocket or swap quote
Pricing touches three different products, and each answers a different question.
| REST price endpoints | Datastream price rooms | Raptor swap quote | |
|---|---|---|---|
| Question it answers | What is the price now? | How is the price moving? | What will I receive for this trade size? |
| Delivery | Request and response | Push over WebSocket | Request and response |
| Accounts for trade size and route | No | No | Yes |
| Typical use | Watchlists, portfolio marks, token pages, startup state | Live tickers, charts, alerts | Swap buttons, bots before sending a transaction |
| Access | Data API key, any plan | Premium plan or higher | See the Raptor Swap API page |
For a screen that stays open, start from a REST snapshot and then subscribe to a price room; the Solana price WebSocket guide shows how to combine the two around reconnects. Polling getPrice every second for a live ticker spends one request per call and still lags a push feed.
For anything that ends in a transaction, request a quote from Raptor with the input mint, output mint and amount in base units; the Solana swap API guide walks the quote, build, sign and send flow. A quote reflects the route, pool depth and your trade size; a spot price reflects none of them. A token showing $0.0042 on a $3,000 pool will not fill a $5,000 buy anywhere near that price.
Response shapes
These shapes are trimmed from the SDK 0.5.0 type definitions. Fields not shown exist but are not needed here.
getPrice(mint, true) returns a PriceData object. lastUpdated is in milliseconds. priceChanges is only present when you pass true.
{
"price": 150.12,
"priceQuote": 1,
"liquidity": 29450616,
"marketCap": 91000000000,
"lastUpdated": 1791380000000,
"priceChanges": {
"1h": { "priceChangePercentage": 0.42 },
"24h": { "priceChangePercentage": -2.31 }
}
}
getMultiplePrices returns the same object per mint, keyed by mint address. A mint with no price can be missing from the response, so join by key and never by array position.
{
"So11111111111111111111111111111111111111112": { "price": 150.12, "liquidity": 29450616, "marketCap": 91000000000, "lastUpdated": 1791380000000 },
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": { "price": 1.0, "liquidity": 12400000, "marketCap": 8900000000, "lastUpdated": 1791379995000 }
}
getTokenInfo returns token, pools, events, risk and transaction counts. Each pool carries its own price and liquidity in USD and in the quote token:
interface PoolInfo {
poolId: string;
market: string; // e.g. "raydium-clmm", "orca", "pumpfun-amm"
quoteToken: string; // mint of the quote side
price: { usd: number; quote: number };
liquidity: { usd: number; quote: number };
marketCap: { usd: number; quote: number };
lastUpdated: number; // ms
txns?: { buys: number; sells: number; total: number; volume: number; volume24h: number };
}
getPriceHistory returns current plus optional 3d, 5d, 7d, 14d and 30d numbers. A young token simply lacks the older keys, so treat each one as optional.
Run the example
Create an empty directory and install the SDK and a TypeScript runner:
npm init -y && npm pkg set type=module
npm install @solana-tracker/data-api@^0.5.0
npm install --save-dev tsx typescript @types/node
node --env-file=.env --import tsx index.ts
Put your key in .env next to the script. Create the key in the Data API dashboard and never commit the file. TOKEN_MINT defaults to wrapped SOL, and WATCHLIST takes comma-separated mints (the script chunks lists longer than 100).
ST_API_KEY=
TOKEN_MINT=So11111111111111111111111111111111111111112
WATCHLIST=
The script prints every pool for one token, sorted by liquidity, then prices a watchlist in batches. The SDK has no request timeout and does not retry, so each call goes through a small wrapper that adds both and honors retryAfter on rate limits.
import { Client, DataApiError, RateLimitError, ValidationError } from "@solana-tracker/data-api";
import type { PriceData } from "@solana-tracker/data-api";
const apiKey = process.env.ST_API_KEY?.trim();
if (!apiKey) {
console.error("Missing ST_API_KEY (create one at https://www.solanatracker.io/account/data-api)");
process.exit(1);
}
const BASE58 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
const mint = process.env.TOKEN_MINT?.trim() || "So11111111111111111111111111111111111111112";
const watchlist = [...new Set((process.env.WATCHLIST?.trim() ||
"So11111111111111111111111111111111111111112,EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v,JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN"
).split(",").map((m) => m.trim()).filter(Boolean))];
for (const m of [mint, ...watchlist]) {
if (!BASE58.test(m)) { console.error(`Not a valid mint address: ${m}`); process.exit(1); }
}
const client = new Client({ apiKey });
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
async function withRetry<T>(label: string, fn: () => Promise<T>, attempts = 4, timeoutMs = 15_000): Promise<T> {
for (let attempt = 1; ; attempt++) {
let timer: NodeJS.Timeout | undefined;
try {
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(new Error(`${label} timed out`)), timeoutMs);
});
return await Promise.race([fn(), timeout]);
} catch (error) {
const retryable = !(error instanceof ValidationError) &&
(!(error instanceof DataApiError) || error instanceof RateLimitError || (error.status ?? 500) >= 500);
if (!retryable || attempt >= attempts) throw error;
const retryAfterMs = error instanceof RateLimitError && error.retryAfter ? error.retryAfter * 1000 : 0;
await sleep(Math.max(Math.min(8_000, 500 * 2 ** (attempt - 1)), retryAfterMs));
} finally {
clearTimeout(timer);
}
}
}
const usd = (n?: number) => (typeof n === "number" && Number.isFinite(n) ? `$${n < 0.01 ? n.toPrecision(3) : n.toFixed(2)}` : "n/a");
const short = (a: string) => `${a.slice(0, 4)}…${a.slice(-4)}`;
try {
// 1. Every pool for one token, deepest first.
const info = await withRetry("getTokenInfo", () => client.getTokenInfo(mint));
const pools = [...(info.pools ?? [])].sort((a, b) => (b.liquidity?.usd ?? 0) - (a.liquidity?.usd ?? 0));
console.log(`${info.token.symbol} (${short(mint)}): ${pools.length} pool(s)`);
console.table(pools.slice(0, 10).map((p) => ({
market: p.market,
pool: short(p.poolId),
priceUsd: usd(p.price?.usd),
liquidityUsd: usd(p.liquidity?.usd),
ageSec: Math.round((Date.now() - p.lastUpdated) / 1000),
})));
// 2. Watchlist prices, 100 mints per request, joined by mint key.
const prices: Record<string, PriceData> = {};
for (let i = 0; i < watchlist.length; i += 100) {
const group = watchlist.slice(i, i + 100);
Object.assign(prices, await withRetry("getMultiplePrices", () => client.getMultiplePrices(group, true)));
}
console.table(watchlist.map((m) => {
const p = prices[m];
if (!p || !Number.isFinite(p.price)) return { mint: short(m), priceUsd: "unavailable" };
return {
mint: short(m),
priceUsd: usd(p.price),
liquidityUsd: usd(p.liquidity),
change24h: p.priceChanges?.["24h"]?.priceChangePercentage?.toFixed(2) ?? "n/a",
};
}));
} catch (error) {
const status = error instanceof DataApiError ? ` (HTTP ${error.status})` : "";
console.error(`Request failed${status}: ${error instanceof Error ? error.message : String(error)}`);
process.exitCode = 1;
}
The companion project does the same with aligned tables, adds the getPrice snapshot with 1h and 24h change, the getPriceHistory points and the pool price spread.
Production pitfalls
There is no single price. A token with ten pools has ten prices. They usually agree within a fraction of a percent on liquid tokens and can disagree by a lot on new ones. Print the pool spread, as the example does, and decide which pool you trust instead of taking pools[0]. Show the pool when a number is pool-specific.
Thin pools produce plausible numbers. A pool with $800 of liquidity can report a price that looks fine and cannot absorb a real order. Set a liquidity floor before a pool's price is allowed to drive anything. For a trade, ask for a quote instead.
Missing is not zero. A mint can be absent from a batch response, or come back with a non-finite price. Replacing that with 0 makes an outage look like a total loss in a portfolio view. Render it as unavailable and keep the last known value with its timestamp.
Check freshness. lastUpdated tells you when the price was last observed. A token that has not traded for hours still returns a price. Display the age next to the number and treat old values differently from fresh ones.
Batch and cache on the server. Deduplicate mints before batching, chunk at 100, and cache shared results so many viewers of one token cause one upstream request. Each REST call counts against your plan's monthly request allowance; streaming does not.
Retry with limits. Invalid keys (401 or 403) and validation errors need a configuration fix, not a retry. Rate limits and 5xx responses get capped exponential backoff with jitter, honoring retryAfter. Keep the last good value on screen while you retry.
Keep the key on the backend. A key shipped in browser code is visible to anyone. Proxy price calls through your server and return only the fields the frontend needs.
Spot price is not risk-free. A clean price says nothing about mint authority, holder concentration or liquidity locks. Gate automated actions on a risk check; the rug check guide covers that.
FAQ
How many tokens can I price in one request?
Up to 100 with getMultiplePrices or postMultiplePrices. The SDK rejects larger arrays with a ValidationError before sending, so split longer lists into chunks of 100 and merge the results by mint.
Why does getPrice differ from the price shown for a specific pool?
getPrice returns one snapshot for the token, while getTokenInfo lists each pool's own price. Pools drift apart when liquidity is thin or a trade has just moved one of them. Use the per-pool data when the venue matters.
Can I get the price of a token at a past time?
Yes. getPriceAtTimestamp(mint, unixSeconds) returns the price near that moment and the pool it came from, and getPriceRange(mint, from, to) returns the lowest and highest price in a window. For a full series, use candles from the chart endpoint.
Is the API price what I will get in a swap?
No. A swap fills against specific pools at your trade size, with fees and slippage. Request a quote from the Raptor Swap API for an executable amount.
Should I poll for a live ticker?
Only for slow-changing screens. For tickers and alerts, subscribe to a Datastream price room on Premium or higher and use REST for the initial value and for recovery after a reconnect.
Does the API cover new pump.fun tokens?
Tokens are priced once their pools are indexed, including launchpad bonding curves and the AMM pools they migrate to. Expect the pool list and the main pool to change over a new token's first hours.
References
- Get Solana token prices and OHLCV charts (guide)
- Get token price
- Get multiple token prices
- Get historic price information
- Get price at a specific timestamp
- Get lowest and highest price in a time range
- Get token information
- Raptor: get swap quote
- @solana-tracker/data-api on npm
Companion project
The companion project prints a per-pool price and liquidity table with the pool spread, a price snapshot with change percentages, coarse history points and a batch watchlist table. It reads ST_API_KEY, TOKEN_MINT and WATCHLIST from .env.
cp .env.example .env && npm install && npm start