# 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" otherwise (clear means "no provable trap detected", NOT "safe"), 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. 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. 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) but the flags are Base-tuned, so a non-base value answers 422 rather than Base-calibrated flags for another chain. Output: verdict avoid|clear + 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. "avoid" = a listed trap was PROVEN at this block (see `triggers`); "clear" = none of them fired. "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, a counter-asset pot smaller than 5% of the size you asked about (the pot is an upper bound on what a sale can return), 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. 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. 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, singletonScanTruncated, 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. `poolsFound: null` with `factoriesChecked: 0` means discovery did not run. 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). Sign the x402 payment as usual; nothing is settled on-chain for that first call. 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":"0x4200000000000000000000000000000000000006","sizeUSD":"5000"}' 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