# Plexa > Plexa is a pay-per-call economic-safety API for trading agents on EVM DEXes. Two POST endpoints on one on-chain liquidity engine: /v1/quote ($0.02) returns the REALIZABLE fill price for a swap at a given size (price impact, depth, route, worst-case slippage — not a mid/spot price feed), and /v1/pretrade/check ($0.05) returns a two-level economic-safety answer for a token before you buy it: a verdict that is "avoid" ONLY when a listed trap is proven on-chain at that block, "clear" when none fired AND the liquidity sweep reached a conclusion (clear means "no provable trap detected", NOT "safe"), and "unknown" when the sweep did not reach one — an incomplete check is never reported as a clean one, plus a risk_profile of structural facts. Payment is x402 v2 (scheme "exact", USDC), settle-before-serve. No API key, no signup. Endpoints (both POST-only, JSON body; GET returns 405 with instructions): - POST https://api.getplexa.com/v1/quote — $0.02 per call. Input: {tokenIn, tokenOut, sizeIn and/or sizeUSD (if both, sizeUSD wins), chain?}. Output: executablePrice, priceImpactBps, depthAtSize, worstCaseSlippageBps, route (per-leg dex/pool/fee), confidence, plus a MARKET CROSS-CHECK: `marketRoute` {source, amountOut, impactBps, splitTop[{venue, share}]} is what an aggregator offers for the SAME input in the same minute, `divergenceBps` is how far OUR output is from it (negative = we give less), and `singleRouteOnly: true` marks the case where that gap exceeded the threshold — the market filled by SPLITTING or on a venue outside our coverage, while we answer with the best SINGLE route. We do not substitute their number for ours: we are a source with a cross-check, not a proxy. Aggregator silent or without a route → `marketRoute: null` and `marketRouteUnavailable: "unavailable:"`; the rest of the answer is unaffected. Sizes are STRINGS ("5000", not 5000). Accepted `chain` values (case-sensitive, exact): base | polygon | matic | arbitrum | arbitrum-one | arb | eip155:8453 | eip155:137 | eip155:42161 — omit for base. Canonical on-chain quoters: Uniswap v3 QuoterV2, Aerodrome, Aerodrome Slipstream (concentrated liquidity), plus Uniswap v4 simulated per pool through the canonical V4Quoter so hooks execute. `route.legs[].dex` names which one filled each leg; `venuesConsidered` counts the direct pool quotes we obtained for the pair. Routing: the answer is the best SINGLE route — direct pools plus one-hop routes through the chain's major assets. We do not split an order across venues, and an aggregator that DOES split can therefore pay more than we quote: on a trapped token we saw its fill come out as our own best route PLUS our own second-best route added together, so treat our number as the best single road, not as the ceiling of the market. When that route pays less than 90% of the position (or no route exists at all), we ENUMERATE the token's counter-assets from chain logs — the same enumeration the exit-liquidity pot uses — and try up to 4 of them, most pools first, including the chain's NATIVE currency (address(0) pools in Uniswap v4, which no factory reports). In the pre-trade check that search also runs when the exit pot comes out under 5% of the position: a position you cannot get out of is a reason to ask whether we bought it by the right road. That search has its own time budget: if it does not finish, the answer is the best route found without it, never an error. Gas conditions: prices here are measured with a REAL trade's gas limit (12,000,000 units), not with the node's default (the block gas limit) — some Uniswap v4 hooks set their fee from the gas available to the caller (we measured one charging 0% above ~18.7M gas and 9% below), so a quote taken with the node default can promise what no trade can get; a venue whose quote does not fit that limit is dropped from the answer instead of being quoted under conditions a trade cannot reproduce. flags[].code vocabulary (closed list): NO_ROUTE — no venue we cover could fill this pair at this size — the answer carries no price, not a bad one · SINGLE_POOL — priced by ONE venue: no independent cross-check of the number exists in this answer · THIN_LIQUIDITY — the book is thin relative to your size — the fill moves the price a lot · SOURCE_DIVERGENCE — the venues we asked disagree with each other; on a pair with both a deep pool and dust pools this is normal dispersion, not proof of error · HIGH_IMPACT — price impact above 500 bps at THIS size — the size is the cause, not the token · MULTI_HOP_ONLY — no direct pool exists; the route goes through an intermediate token, so two fees and two impacts apply · SPOT_REFERENCE_DEGRADED — no venue offered a mid we can defend against the executable price, so spotPrice, priceImpactBps and worstCaseSlippageBps are NULL rather than guessed · DEGENERATE_POOL_EXCLUDED — a pool offering more output than the whole supply of the output token was dropped from the route set (reason `pool-degenerate-price`) — it is not a price, it is broken pool state · MARKET_DIVERGENCE — our output differs from an independent aggregator by more than the threshold; see marketRoute and divergenceBps for their number and the gap · ROADS_UNREAD — the road search could not enumerate every source of this token's pairs — a road may exist and stay untried, so this price is the best of what we could reach, not proof of the best that exists · ROAD_SEARCH_OVER_BUDGET — the road search ran out of its time budget before finishing — same meaning as ROADS_UNREAD, with the cause named · ROAD_SEARCH_NOT_RUN — the direct route already paid enough, so no road search was run: we did not look for an exotic road that might pay more. severity is info|warn|critical and describes how much the flag should weigh, not how bad the token is. The chain you QUOTE on is independent of the network you PAY on. - POST https://api.getplexa.com/v1/pretrade/check — $0.05 per call. Input: {token, sizeUSD?, chain?} — sizeUSD is a STRING ("1000", not 1000); `chain` accepts the same exact values as /v1/quote (base | polygon | matic | arbitrum | arbitrum-one | arb | eip155:8453 | eip155:137 | eip155:42161, case-sensitive). Served on: base, polygon, arbitrum. Per network — base (13 liquidity factories + uniswap-v4/pancakeswap-infinity, priced on uniswap-v3/aerodrome/aerodrome-cl-1/aerodrome-cl-2/aerodrome-cl-3/sushiswap-v3/uniswap-v2/sushiswap-v2/pancakeswap-v2/baseswap/alienbase/swapbased/uniswap-v4); polygon (15 liquidity factories + uniswap-v4, priced on uniswap-v3/sushiswap-v3/quickswap-v3/quickswap-v2/uniswap-v2/sushiswap-v2/apeswap/uniswap-v4); arbitrum (10 liquidity factories + uniswap-v4, priced on uniswap-v3/sushiswap-v3/ramses-v3/camelot-v3/sushiswap-v2/camelot-v2/uniswap-v2/uniswap-v4). The exit pot and the quote are measured on DIFFERENT venue lists by design: the pot only needs pool discovery and balances, a quote needs a quoter we have confirmed against the live market. Venues that count toward the exit pot but are NOT in the price: base — pancakeswap-v3, pancakeswap-infinity; polygon — retro, ramses-v3, w-dex, dooar, vulcandex, fraxswap, wault, polycat; arbitrum — pancakeswap-v3, ramses-v2, solidly-v3. On those the quote can be worse than the market while the safety verdict still sees that liquidity. A network that is not listed answers 422 — we do not publish a verdict where we have not measured the exit. Output: verdict avoid|clear|unknown + triggers[] + confidence + reasons + risk_profile + the 5 raw signals (liquidity, concentration, age, fee-on-transfer/honeypot, oracle sanity) + an executable quote sub-result. verdict semantics changed 2026-08-01 (v2): avoid = provable trap only; ok/caution are gone. 2026-08-27 (P67): a THIRD value "unknown" was added — it takes over the cases that used to answer "clear" while `liquidityCoverage.conclusive` was false. No existing value changed meaning; code branching on "avoid" is unaffected, and code branching on "clear" now gets strictly fewer, better-founded clears. 2026-09-21 (P96): a clear verdict now also requires the MEASURED sale of your position to return at least 90% of the size you asked about. Below that the answer is "unknown" with the reason exit-below-90pct — no trap was proven, but the exit we measured is not there at this size. Again strictly fewer clears; avoid is untouched. "avoid" = a listed trap was PROVEN at this block (see `triggers`); "clear" = none of them fired AND the liquidity sweep reached a conclusion; "unknown" = none fired but the sweep did NOT reach a conclusion (`liquidityCoverage.conclusive: false`), so absence of a trap is not established — see the `exit-coverage-incomplete:` line in `reasons`. "clear" is NOT a safety rating and NOT a prediction — structural risk lives in `risk_profile`. The closed trap list is NO_EXIT_VENUE, EXIT_LIQUIDITY_DRAINED, TRADING_DISABLED: a pool that does not exist, an exit pot smaller than 5% of the size you asked about (`liquidityCoverage.exitPotBound` says what that number IS: `measured` when the sweep walked every source it knows about, `lower` when it stopped once the drain question was already answered — then the pot is a FLOOR, and `exitPotBoundReason` names the venue and how many of its pools were asked out of how many exist. Unasked pools can only RAISE the pot, so a trap verdict always rests on a fully measured one; since 2026-08-27 the pot is measured across up to TWO hops: pairs against the chain's stable and gas assets plus pairs against any other counter asset whose own exit we confirmed by quoting it — a token sellable only against some third asset is no longer called a dead end), or the token's own trading gate reading false. Structural risk (young token, one wallet holding everything, a single pool, a blacklist function) is NOT a verdict — it travels in risk_profile as numbers with plain-language notes, because a measurement on 40 outcome-labelled Base tokens showed those factors mark live and dead tokens at the same rate. `confidence`: On "avoid" this is confidence IN THE TRIGGER, which is a measurement taken this call, so it is always "high" — a check that failed elsewhere does not make the measured trap less true. On "clear" it is how COMPLETE the checks were: fewer axes computed, lower confidence. On "unknown" it is capped at "low" by construction: the check the verdict depends on did not finish, so a high number there would contradict the verdict word next to it. Either way the gaps are reported explicitly — see `reasons` and the null fields in `risk_profile`. Concentration is computed for tokens of ANY age: top-1/top-10 share of supply, computed by us from holder balances and on-chain totalSupply (indexed holders source, with an on-chain transfer replay for recent tokens). The data age travels in the response (`asOfBlock`, `cachedAgeSec`); the exact holder count is reported only when it is known (`holdersExact`). During a vendor outage the flag answers `unknown` with an explicit reason — never a clean default. Roads are named, not implied: exitLadderStatus describes ONLY the roads in exitLadderScope ("uniswap-v4-direct" — direct Uniswap v4 pools), while the road the pot was actually measured on is named in exitReachRoad ("direct" or "via "). They are often different, and "unsellable" beside a non-empty pot means exactly that, not "this token cannot be sold". For the same reason exitPoolInventoryUsd stops being an upper bound whenever the v4 ladder ran (its v4 share is then the ladder's reach, not inventory) and it never includes the second leg of a two-leg sale. Response envelope (pretrade): `chain`, `token`, `verdict`, `triggers`, `confidence`, `reasons`, `risk_profile`, `flags`, `quote`, `sizeUSD`, `disclaimer`, `ts`, `validUntil`, `coverage`, `liquidityCoverage`, plus the six context blocks below. `ts` is when we computed it (epoch ms) and `validUntil` until when asking again returns the same answer; `disclaimer` is the informational-not-advice statement, carried in the body so it cannot be stripped by a proxy. Beyond the verdict, every pretrade response carries six blocks of context, and each block carries a `note` string explaining what it does and does not prove. - `identity`: `name`, `symbol`, `decimals`, `totalSupply` and `totalSupplyRaw` (the same figure in base units, unrounded) — read straight from the token contract at this block, not from a listing database that can be stale or wrong about a fork. - `valuation`: `fdvExecutableUsd` is fully-diluted value at the price YOUR size actually executes at, and `basis` spells out the formula. It is deliberately a DIFFERENT quantity from a market-data site's market cap, for two reasons: the executable price is worse than spot on a thin book, and totalSupply is THIS chain's supply. The vendor's global figure travels in the same response as `market.marketCapUsd` — compare them, do not equate them; for a bridged token ours is legitimately the smaller number. - `ownership`: `ownerAddress`, `ownerRenounced`, `isMintable`, `creatorAddress`, `creatorBalance`, `creatorSharePct`. `ownerRenounced: null` means no owner() answered — that is UNKNOWN and NOT renounced. `isMintable` is a bytecode heuristic: proxies and assembly dispatchers can hide a mint. `creatorAddress` is inferred from the sender of the first transfer — a candidate, not a proven deployer. - `dormancy`: `topHolderIdleDays`, `lastTopHolderMoveBlock`, `headBlock` — how long the top holder has sat still. It rests on the holder axis, so when that axis produces nothing the block is null and its `note` says why. - `market`: third-party data republished as-is — `priceUsdSpot` (a SPOT print, NOT the price your order gets: that one is `quote.executablePrice` and depends on sizeUSD), `volume24hUsd`, `marketCapUsd`, `holderCount`. We measured none of it. - `sources`: the attribution map, described next. `sources` attributes EVERY field of the response, so an agent can tell what we measured from what we republished: `measured` — ours, read off the chain on this call; `derived:` — ours, computed from other fields of this same response; `vendor:` — somebody else's number, republished as-is and signed as theirs; `unavailable:` — no value, AND the reason why. That last one is the point of the map: a missing number that names its own gap cannot be mistaken for a clean result. Read `sources` before trusting any single number. Alias paths for /v1/pretrade/check: /v1/token-safety, /v1/rug-check — the SAME endpoint (same engine, same price, same request and response, one shared free trial), not a different product. They exist because agent catalogs index by URL path. Every response carries `validUntil` (epoch ms): asking again before it returns the same answer — it is a statement about when WE recompute, not a promise that the market held still. /v1/pretrade/check additionally carries `coverage` {axesExpected, axesReporting}: how many of the 5 axes produced a result. Gaps stay visible in `reasons`, in `flags[].severity` and as nulls in `risk_profile` — `coverage` is the summary, not a replacement. It also carries `liquidityCoverage` {conclusive, poolsFound, factoriesChecked, unmeasuredVenues, unmeasuredVenueNames, venuesUnread, singletonPoolsSeen, singletonPoolsCounted, singletonPoolsRejected, singletonPoolsHooked, singletonPoolsHookRefused, singletonHookFlags, singletonScanTruncated, secondHopAssetsSeen, secondHopAssetsCounted, secondHopAssetsRejected, secondHopTruncated, secondHopUsd, secondHopAssets, exitPotBound, exitPotBoundReason, exitVenuesCounted, singletonPoolsDiscovered, singletonPoolsUnasked, exitLadderTopUsd, exitLadderBrokeAtUsd, exitLadderPoolsProbed, exitLadderStatus, exitLadderScope, secondHopStatus, exitPotUnquotedUsd, exitVenuesUnquoted, exitPotBasis, exitReachUsd, exitReachStatus, exitReachRoad, exitPoolInventoryUsd, asOfBlockFirst, asOfBlock}: `conclusive: true` with `poolsFound: 0` means every covered factory was queried and no pool exists — that is a measurement, and it is what raises NO_EXIT_VENUE. `conclusive: false` means an AMM we cannot measure pool-by-pool (Uniswap v4 and other singleton AMMs) holds this token, so the exit triggers are not raised on this call — and since P67 the verdict is then `unknown`, never `clear`: a sweep that did not reach a conclusion cannot report the absence of a trap. `poolsFound: null` with `factoriesChecked: 0` means discovery did not run. Roads out that are NOT the chain's stable/gas assets are covered too: `secondHopAssetsSeen` counts counter assets the factories list a pool for with this token (read from their own pool-creation events, so no list of `good` assets is involved), `secondHopAssetsCounted` how many of those roads we confirmed by quoting that asset's own exit, `secondHopAssetsRejected` how many we could not confirm (they add nothing AND make the exit inconclusive), `secondHopUsd`/`secondHopAssets` what they contributed and which assets carry it, `secondHopTruncated` whether more roads existed than the per-call cap. That search runs only when the stable/gas pot is below the drain floor — above it another road cannot change the answer. For singleton AMMs the sell is SIMULATED per pool, and the three outcomes are separate counts: `singletonPoolsCounted` (simulation succeeded, proceeds are inside exitLiquidityUsd), `singletonPoolsRejected` (the chain rejected the sale — a measurement about that pool, not a gap in our data), and pools left unquestioned because the scan hit its work cap, flagged by `singletonScanTruncated`. `singletonPoolsSeen` is the total found and `singletonPoolsHooked` how many run a hook inside the swap. All five are null only when the liquidity axis produced no metrics at all. `asOfBlockFirst` and `asOfBlock` are the Base block heights at the start and the end of the liquidity reads: equal means the axis was read inside one block and is reproducible at that block, different means no single block describes it and the gap is the size of that uncertainty. `risk_profile.concentration` carries `ageSec` and `stale`. `ageSec: null` with `stale: false` means the holder data was read on this call. A number in `ageSec` means it was measured that many seconds ago; `stale: true` additionally means the holder source was unreachable right now, so the LAST KNOWN measurement is reported instead of nothing, and `confidence` drops. `stale: null` means there is no holder value at all in this response — neither fresh nor stored — so there is nothing for the freshness flag to describe. The stored value survives a restart and keeps its real age. Concentration is level-2 structural data — no verdict trigger reads it — so an aged value cannot change `avoid`/`clear`. First call from a new wallet is FREE — the full response, every field, same engine as a paid call. No trial tier, no reduced answer: only the COUNT is limited (one per wallet, across BOTH endpoints). Sign the x402 payment as usual; nothing is settled on-chain for that first call. P82 — you do not have to take that on trust: every 200 response carries `freeTrialApplied` — true when THAT call was served free, false when it was billed. How to pay (x402 v2): an unpaid POST returns HTTP 402 with payment requirements base64-JSON-encoded in the PAYMENT-REQUIRED response header. Sign an EIP-3009 USDC transfer authorization (EVM networks) or submit a partially-signed SPL USDC transfer transaction (Solana) for one accepted network and retry the same POST with the PAYMENT-SIGNATURE header. Payment networks: Base mainnet (eip155:8453), Polygon PoS (eip155:137), Arbitrum One (eip155:42161), Solana mainnet (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp). Client SDK: npm "@x402/fetch" (wrapFetchWithPayment). MCP server: npm "@getplexa/mcp" (tools plexa_quote + plexa_pretrade_check with payment built in). Example (unpaid probe — returns 402 with payment requirements): curl -X POST https://api.getplexa.com/v1/quote \ -H 'content-type: application/json' \ -d '{"tokenIn":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","tokenOut":"0xE014d2A4da6E450f21b5050120D291e63c8940FD","sizeUSD":"5000","chain":"base"}' Responses are informational on-chain liquidity data, not financial advice. Contact the people running this API: support@getplexa.com (a human reads it), https://t.me/getplexa_bot (Telegram), https://github.com/plexa-xyz (source: reference agent, MCP package). No SLA and no support tier — this is a pay-per-call API, not a subscription. ## Docs - [OpenAPI 3.1 spec](https://api.getplexa.com/openapi.json): request/response schemas for both endpoints, including the 402 flow - [x402 discovery pointer](https://api.getplexa.com/.well-known/x402): machine-readable service pointer - [Contact card](https://api.getplexa.com/contact): who runs this API and how to reach them (JSON) - [Site](https://getplexa.com): human docs and examples - [MCP server](https://www.npmjs.com/package/@getplexa/mcp): npx @getplexa/mcp — MCP registry name com.getplexa/mcp ## Optional - [Health](https://api.getplexa.com/health): liveness probe (free) - [x402 protocol](https://github.com/coinbase/x402): payment protocol specification