

Anmol·Mar 20, 2026
First guess
Just like any normal person, my first thought was to ask Claude. I had to physically sit on my hands to control myself. The kind of shit you have to do to control the urge. I think I might be an addict but that is a story for another article. Moving onto my first original thought in about 6 months. My assumption was that the Price API reads on-chain pool state and derives price from reserves. For a standard Raydium pool, you calculate price from the reserve ratio, if the pool holds 10,000 SOL and 25,000,000 JUP, the implied price is 0.0004 SOL per JUP, quick mafs. For concentrated liquidity pools on Orca or Meteora, you’d readsqrtPriceX64 from the pool account and convert. Either way, you’re deriving price from pool state at a given slot.
I tested this by using Jupiter’s quote API with onlyDirectRoutes=true to force a single-pool quote for a bunch of tokens, then compared the implied price against what the Price API returned.
For liquid tokens at small sizes (1 SOL, ~$90), the difference was tiny. SOL-JUP was off by like 0.15%. BONK, POPCAT, MEW, all within 0.1-0.6%. I was almost ready to call it a day and assume pool pricing was good enough.
Then I tried a mid-cap at a larger trade size, around $9,000 worth. Off by over 10%. The liquidity wasn’t deep enough to absorb the size so the single-pool price and the actual market price diverged hard.
That’s the thing about pool-state pricing. It tells you what the next trade would get at a specific size on a specific pool. But that’s a theoretical price, it changes depending on which pool you look at, how much you’re trading, and where the LPs decided to concentrate their liquidity. At small sizes on liquid tokens it looks fine. Scale up or go to thinner markets and it falls apart.

Ok, maybe it averages them?
Fine. Next guess would be that it maybe takes a weighted average across all the pools. Weight each venue by trading volume, combine them into one number to get a price. That’s what most off-chain pricing services do, felt safe enough. But knowing myself, I wasn’t going to build a whole volume-weighted aggregator to test this. Two guesses in and I figured I should just bend the knee and hit up the engineer that actually built the latest version of the API.What V3 actually does
So here’s what he told me. V3 uses the last swapped price. The most recent actual trade for that token, across every DEX Jupiter aggregates. Not a TWAP, not a weighted average, not some implied mid-price from pool state. Just the latest trade someone actually made. It seemed too simple to be true but the more that I thought about it, the more sense it made. TWAPs and weighted averages factor in older prices. If a token’s price just moved 8% in the last block, a TWAP is still catching up because it’s averaging in prices from five minutes ago. Last-swap gives you what someone actually paid right now, which is what most integrators care about. But obviously if the price is one trade, what stops someone from making that one trade a bad one? Wash trade, bot, whatever. That’s handled by a filtering layer I’ll get to in a second. First, the part that nerd-sniped me the most.But how does it get a USD price?
Knowing the last swap happened doesn’t give you a USD price. If someone swapped 1 JUP for 0.0018 SOL, you know the JUP/SOL ratio. But you don’t know what JUP is worth in dollars unless you know what SOL is worth in dollars. Jupiter solves this by anchoring to a small set of tokens whose USD price comes from external oracles. SOL is the primary anchor. Its dollar value is a known quantity, externally verified, continuously updated. From there it’s just math. You follow the swap ratios. 1 JUP → 0.0018 SOL. SOL = $90.35 (from oracle). So JUP ≈ $0.1626. But the chain doesn’t stop at one hop. Say there’s a token, call it $THING, that has never been swapped against SOL or USDC. It only has a pair against BONK. And BONK has a pair against SOL. And SOL has an oracle price. So:$THING → BONK → SOL → USD.
The price of $THING is derived through a chain of swap ratios, all anchored back to one trusted root. Every token price in Jupiter’s system is basically a path, a sequence of exchange rates that eventually terminates at something an oracle can vouch for.

Filtering out bad prices
Ok so at this point I had a pretty good picture of how the price gets derived. But anyone who’s been on Solana for more than a week has seen a pumpfun token where the chart looks like organic price action and it’s actually one wallet talking to itself. If the price is the last trade, what stops a bad actor from just being the last trade?
fetch callback. That’s easy to take for granted when you’re just parsing JSON.
On top of the heuristic filters, for certain token categories like stables, yield bearing tokens, and big caps like SOL, there’s also realtime outlier detection. It still uses the last traded price as the base, but filters out outlier trades as they happen. From what I was told, the plan is to possibly generalize this to all tokens eventually. I imagine that’s going to be tough though, how do you tell the difference between a fake pump and a real one in realtime?
The response
blockId is a Solana block reference, compare it to the current block and you know how fresh the price is. decimals saves you a getAccountInfo call that I’ve seen in way too many codebases. And priceChange24h covers the thing most integrators were computing themselves, badly, because everyone rolls their own 24h window slightly differently.
One thing to keep in mind, and this comes up a lot especially from traders and arbers using our APIs: what the Price API returns is a lagging indicator, it’s the last trade that already happened. If you need the exact execution price for a specific amount right now, use the /quote endpoint on the Swap API. Price API is for display and reference, /quote is what you use when you’re actually about to swap.
Things worth knowing if you’re integrating
On missing tokens in the response. When a token doesn’t show up in the response, it might have swap data, it’s been traded. But something about the market around it didn’t pass the heuristic filters, either the liquidity depth, trading pattern, or holder distribution raised a flag. Can also mean the token hasn’t been swapped in 7+ days so there’s no recent swap to derive from. On price freshness. The price updates when someone makes a qualifying swap. For popular tokens that’s every few seconds. For long-tail tokens theblockId gap can be big, the price might be minutes or hours old. That’s not the API being slow, that’s just nobody trading. Worth showing price age in your UI for smaller tokens.
On discrepancies with other price sources. If you’re seeing different numbers from Birdeye, Dexscreener, or CoinGecko, it’s a methodology difference, not a bug. Those platforms typically use TWAPs or volume-weighted composites, which means they’re averaging over a window of time so the price looks smoother. V3 gives you the last actual trade, so it’s going to be more reactive to recent swaps. Neither is wrong, they’re just answering different questions.
Why I wrote this up
I could have kept going without looking into any of this. The API works. The integrations I help build work. Nothing was broken. Nobody asked me to spend a weekend on this. But there’s a difference between using a tool and understanding it. Everything I build and debug going forward is a bit better now that I know what’s actually happening under the endpoint. Not dramatically. Just enough to not be a very helpful parrot. If you use an API every day, try this: explain, from memory, what happens between the request and the response. Not what params to pass. Not how the response looks. What the system does. If you hit a gap, that’s a good weekend.api.jup.ag/price/v3?ids=So11111111111111111111111111111111111111112 if you want to look for yourself.
Price API Guide: Video walkthrough on X