The pool card and its JSON
Two things on this site are meant for other sites: a card showing one pool's suggested range, which any page may put in a frame, and the same figures as JSON, which any site's code may read. Neither needs a key or an account. Both are described here as the code serves them, and tests hold this page to that code: every parameter it lists is one the address is read with, and every field one the answer is checked against before it is sent.
The card
/embed/pool is a small page written by hand rather than by the site's framework. It shows the pair with its protocol, fee and network; the suggested range, with the horizon and width it was drawn for; the current price; a line saying it is not financial advice; and a link back to the pool's page here, which opens in a new tab. The range is always drawn for the site's defaults, 30 days at 1σ, whatever anyone's own settings are. A note is added while the current price is outside the range, and another for a v4 pool whose hook may change what a swap costs.
It holds no script and no form, and loads nothing: its own Content-Security-Policy allows nothing but its inline style. Its colours are the site's, light or dark as the reader's system is set. It cannot see the page around it, and there is no parameter to choose.
Give it a frame 360 pixels wide and 220 tall, or 260 tall for a pool whose hook may change what a swap costs, whose card carries that one more line. Every pool's page offers exactly that under "Embed this pool", with the height already right for that pool, and the frame it offers never grows wider than the column it is pasted into.
It is the only page on this site another site may frame. Every other address, this one included, is sent with X-Frame-Options: DENY and frame-ancestors 'none'; the card is sent without the first, and with frame-ancestors *.
<iframe src="https://liquiditywise.com/embed/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8" title="USDC / WETH on LiquidityWise" width="360" height="220" style="border:0;max-width:100%" loading="lazy"></iframe>Content-Type: text/html; charset=utf-8
Cache-Control: public, max-age=300, s-maxage=300
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors *Parameters
The card and the JSON take the same parameters, and name a pool the way the site's own pages do: a v3 pool by its address, as on /pool, and a v4 pool by its id, as on /v4. Exactly one of the two is needed, and which one arrived says which protocol it is. A pool or a network named twice is refused rather than having one of its names picked, and any parameter not listed here is ignored.
The language comes from the address alone, never from the reader's browser or a cookie: the site that places the card chooses it, and the same address is the same answer for everyone who loads it, which is what lets a cache keep it. The card is written in all 10 of the site's languages, and in Arabic it reads right to left.
address- Accepts A v3 pool's contract address:
0xand 40 hexadecimal digits, in upper or lower case. - Otherwise Anything else names no pool, and so does a v3 pool on a network where v3 is not read (Unichain):
400, and nothing is read. id- Accepts A v4 pool's id, the hash of its key:
0xand 64 hexadecimal digits, in upper or lower case. - Otherwise Anything else names no pool:
400, and nothing is read. So does anidbeside anaddress. chain- Accepts A network's slug, from the table below. Left out, the network is Ethereum.
- Otherwise A slug the site does not read is refused, never read as Ethereum:
400. lang- Accepts A language code from the list below, for the card's words and the JSON's
disclaimer; the figures are the same in every language. Left out, English. - Otherwise Any other value, or
langgiven twice, gives English. It is never refused.
The networks, by the slug chain takes:
chain | Network | v3 pools | v4 pools |
|---|---|---|---|
ethereum | Ethereum | read | read |
base | Base | read | read |
arbitrum | Arbitrum One | read | read |
unichain | Unichain | not read | read |
optimism | OP Mainnet | read | read |
polygon | Polygon | read | read |
The languages, by the code lang takes:
enEnglishtrTürkçedeDeutschesEspañolarالعربيةhiहिन्दीzh中文ruРусскийptPortuguêszh-Hant繁體中文
The JSON
GET /api/embed/pool, with the same parameters, answers with the card's figures as JSON, for a site that would rather draw them itself: the same reading, kept just as long. Every figure is a JSON number, and every price is written the way the pool's page writes it — how many price.quote one price.base is worth.
Before an answer is sent it is checked against its schema, and one that does not hold is not sent: the pool is answered as unreadable instead. So an answer with status 200 always has exactly the fields below, no more and no fewer.
Any site's script may read it. Every answer, errors and refusals included, is sent with Access-Control-Allow-Origin: *. It sets no cookie and needs no credential. A plain GET needs no preflight, and none is answered, so send it without custom headers.
Content-Type: application/json; charset=utf-8
Cache-Control: public, max-age=300, s-maxage=300
Access-Control-Allow-Origin: *Every field of a 200 answer, with its JSON type:
protocol"v3" | "v4"v3for a pool named byaddress,v4for one named byid.chain.idinteger > 0- The network's chain id.
chain.slugstring- The network's slug, as
chaintakes it. chain.namestring- The network's name.
poolstring- The pool's address (v3) or id (v4), in lower case.
pair.token0string- The symbol of the pool's first token, as its contract states it: text from a contract anyone can deploy, so escape it before putting it in a page.
pair.token1string- The second token's symbol, the same way.
lpFeePpminteger ≥ 0 | null- The fee the pool pays liquidity providers on a swap, in millionths:
3000is 0.3%.nullwhen a v4 pool's hook sets the fee swap by swap. price.basestring- The token each price is for one unit of.
price.quotestring- The token each price is counted in.
price.currentnumber > 0- The pool's price when it was read.
range.lowernumber > 0- The suggested range's lower edge, as a price.
range.uppernumber > 0- Its upper edge.
range.currentInRangeboolean- Whether the current price is inside the range. When it is not, the card adds a note.
range.lowerTruncatedbooleantruewhen the pool's tick grid cannot reach as far as the band's lower edge, so the range stops short of it.range.upperTruncatedboolean- The same, for the upper edge.
parameters.horizonDaysinteger > 0- The horizon the range was drawn for, in days: always the default, 30.
parameters.standardDeviationMultipliernumber > 0- The width it was drawn for, in standard deviations: always the default, 1σ.
hookMayAlterSwapsbooleantruefor a v4 pool whose hook may change what a swap costs, read from the hook's address;falsefor v3, for a v4 pool with no hook, and for a hook whose permissions leave swaps alone. Where it istrue, the card carries a note.analysedAtstring (ISO 8601)- When the price was read, in UTC to the millisecond — not when this answer was sent, which can be minutes later.
poolUrlstring- The pool's page on this site, at the same horizon and width.
disclaimerstring- What the figures are and are not, in the language
langnames.
{
"protocol": "v3",
"chain": {
"id": 1,
"slug": "ethereum",
"name": "Ethereum"
},
"pool": "0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8",
"pair": {
"token0": "USDC",
"token1": "WETH"
},
"lpFeePpm": 3000,
"price": {
"base": "WETH",
"quote": "USDC",
"current": 3000
},
"range": {
"lower": 2824.2704157479307,
"upper": 3184.336896959513,
"currentInRange": true,
"lowerTruncated": false,
"upperTruncated": false
},
"parameters": {
"horizonDays": 30,
"standardDeviationMultiplier": 1
},
"hookMayAlterSwaps": false,
"analysedAt": "2026-08-21T09:15:00.000Z",
"poolUrl": "https://liquiditywise.com/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8&days=30&sigma=1",
"disclaimer": "For information only, not financial advice. The range is worked out from how far this pool's price moved over the last 30 days: it is not a forecast and not a recommendation."
}Status codes and errors
The card and the JSON answer with the same status codes. Neither ever answers with the site's own error page or with what went wrong inside: the card shows one sentence and its link back, and the JSON answers with an error a script can test, and with disclaimer — except a refusal, which carries the wait instead.
The JSON's answer, with how long it may be kept:
200
The figures: a card with them, or the JSON above.
Cache-Control: public, max-age=300, s-maxage=300400 not-a-pool
The address names no pool the site reads: neither address nor id, or both, or one malformed or given twice, a network the site does not read, or a v3 pool where only v4 is read (Unichain). Nothing was read, and asking again will not change the answer.
Cache-Control: public, max-age=3600, s-maxage=3600
{
"error": "not-a-pool",
"disclaimer": "For information only, not financial advice. The range is worked out from how far this pool's price moved over the last 30 days: it is not a forecast and not a recommendation."
}503 unreadable
A well-formed pool that could not be read just now: a source did not answer in time, or there is no such pool on that network. poolUrl still leads to its page. Asking again in a minute may find it.
Cache-Control: public, max-age=60, s-maxage=60
{
"error": "unreadable",
"poolUrl": "https://liquiditywise.com/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8&days=30&sigma=1",
"disclaimer": "For information only, not financial advice. The range is worked out from how far this pool's price moved over the last 30 days: it is not a forecast and not a recommendation."
}429 rate-limited
Too many requests from one client: see the rate limit below. Retry-After and retryAfterSeconds both say how many seconds to wait. The card answers with a short page saying so, in its own language.
Cache-Control: no-store
Retry-After: 42
{
"error": "rate-limited",
"retryAfterSeconds": 42
}Caching and the rate limit
Every answer says how long it may be kept, by a browser and by a shared cache alike: 300 seconds for the figures; 60 seconds for a pool that could not be read, so one that comes back is not shown as unreadable for long; 3,600 seconds for an address that names no pool, which no later moment will change. A refusal is never kept. Each answer is the same for everyone who asks with the same address, which is what makes keeping it safe.
Behind that, the server keeps each pool's figures for 300 seconds from the moment it read them, for the card and the JSON alike, in every language: every card of one pool, on every page it is placed on, is one reading until it runs out. A pool that could not be read is not kept, and is asked again on the next request. So the price in an answer can be older than the answer: analysedAt says when it was read.
A request that names a well-formed pool reads that pool, so it is counted against the same allowance as the site's own pool pages: 10 requests per client in a window of 60 seconds that starts with the client's first request, a client being the IP address the request reaches the site from. Past it, the answer is 429, with Retry-After. Every such request that reaches the site counts, one answered from the server's keep included; a request that names no pool counts for nothing.
From a browser, a card or a fetch counts against the reader who loads it, not against the site it is placed on — so a page placing more than 10 cards has the ones past that refused, for each reader. From a server, every request you send shares that server's one allowance: keep each answer for the 300 seconds it allows, which costs little, since for most of that time the server would answer with the reading you already have. The limit is there to keep the cost of reading pools down, and nothing here promises it will stay as it is.
Examples
curl -s "https://liquiditywise.com/api/embed/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8"const response = await fetch(
"https://liquiditywise.com/api/embed/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8",
);
const body = await response.json();
if (response.ok) {
// The range, in body.price.quote per one body.price.base
console.log(body.range.lower, body.range.upper, body.price.quote, body.price.base);
console.log(body.disclaimer);
} else if (response.status === 429) {
console.log(`Asked too often: wait ${body.retryAfterSeconds} seconds`);
} else {
console.log(body.error); // "not-a-pool" or "unreadable"
}<iframe src="https://liquiditywise.com/embed/pool?address=0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8" title="USDC / WETH on LiquidityWise" width="360" height="220" style="border:0;max-width:100%" loading="lazy"></iframe>One click selects a whole block, ready to copy.
Terms, in plain words
The figures are measurements, not advice. The range is worked out from how far the pool's price moved over the last 30 days: it is not a forecast and not a recommendation, and every answer says so itself, the card on its face and the JSON in disclaimer. How every figure is made, and what each leaves out, is under "How it works".
The card carries its own link back, "Analysed by LiquidityWise". The JSON has no attribution field, and nothing in the code asks for one; what it carries is poolUrl, the pool's page here, and disclaimer. Shown beside the figures, those two tell a reader where they came from and what they are.
There is no version number and no key, and no promise that the answer's shape will stay as it is or that the site will be up at any given moment. What holds is narrower: an answer is checked against its schema before it is sent, and tests hold this page to that schema and to the code that reads the address, so it describes what is served now. A change to either shows in the code's public history.
The code is open source, under the MIT licence.