API reference ← dashboard github ↗

ParaMEV API

Live MEV data on Monad mainnet over plain JSON — the same data behind the dashboard: confirmed sandwich attacks with USD profit, atomic-arb candidates, extractor rankings and venue-level swap flow. CORS-open, so it works straight from a browser, a notebook, or an agent.

Overview

Base URL: https://mev.parascan.dev. All endpoints are GET, return application/json, and send Access-Control-Allow-Origin: *. No signup is needed for casual use.

curl -s https://mev.parascan.dev/api/summary | jq .totals

Detection methodology (how a sandwich gets confirmed, how profit becomes USD) is documented in the public repo. Counts are floors, never extrapolations; unpriceable sandwiches carry an explicit reason instead of a made-up number.

Building an agent? There's a zero-dependency MCP server over this API — it hands Claude & co. three tools (get_pool_risk, list_sandwiches, get_mev_summary), so an execution agent can check a pool's toxicity before routing a swap.

Authentication

Anonymous access works for light use. For anything sustained — agents, bots, integrations — pass an API key with each request, either as a header or a query parameter:

curl -s -H "X-API-Key: pmk_your_key_here" \
  "https://mev.parascan.dev/api/sandwiches?limit=200"

# or, where headers are awkward:
curl -s "https://mev.parascan.dev/api/summary?key=pmk_your_key_here"

Keys are free — open a GitHub issue with one line about what you're building and you'll get one.

Rate limits

AccessLimitScope
anonymous30 requests / minuteper IP
with API key600 requests / minuteper key (independent of IP)

Limits use a 60-second sliding window. Over the limit you get a 429 with a Retry-After: 30 header — back off and retry. /healthz is not rate-limited.

GET/api/summary

The whole observatory in one call: cumulative totals, top extractors, most contended pools, the 50 most recent sandwiches, 30 most recent arb candidates, and 48 hourly buckets.

{
  "lastBlock": 87173695,
  "lastError": null,
  "totals": {
    "blocks": 5365131,         // blocks analyzed, history + live
    "swaps": 2047479,         // decoded swap events
    "perKind": { "v2": 61474, "v3": 513399, "v4": 714812, "kuru": 757794, "pcs3": 88012, "lfj": 31240 },
    "arbCandidates": 87442,
    "sandwiches": 493,        // confirmed, all venues
    "kuruSandwiches": 43,
    "victims": 521,
    "extractedUsd": 312.44,   // gross, marked to victim price
    "attackerGasUsd": 6.21,
    "pricedSandwiches": 455
  },
  "attackerCount": 83,
  "backfill": { "from": 82168465, "to": 87168464, "blocks": 5000000, "at": 1783820000000 },
  "topAttackers": [ { "key": "0x22c6a8b7…", "sandwiches": 41, "kuru": 9, "amm": 32, "extractedUsd": 38.12, "lastBlock": 87168001 } ],
  "topPools": [ /* most-traded pools */ ],
  "topSandwichedPools": [ /* pools ranked by sandwich count */ ],
  "recentSandwiches": [ /* 50 newest, see object shape below */ ],
  "recentArbs": [ /* 30 newest */ ],
  "hourly": { "2026-07-12T04": { "swaps": 14210, "sandwiches": 2, "arbs": 311 } }
}
GET/api/sandwiches?limit=50

The full sandwich history (not just the dashboard's recent window), filterable. Each row is a confirmed sandwich — see the object shape below.

ParamDescription
limitnumber of rows, default 50, max 1000 (newest first)
poolpool / orderbook address; for v4 either the manager address or the poolId matches
attackerattacker EOA or executor bot contract address
venuev2 | v3 | v4 | kuru | pcs3 | lfj
tokentoken address, native, or a symbol (e.g. WMON) — matches sandwiches on any pool containing it
sinceBlockonly sandwiches closing strictly after this block — the polling cursor (see below)
sinceTsonly sandwiches at/after this time (ms epoch or ISO date)
orderdesc (default) or asc by closing block
curl -s "https://mev.parascan.dev/api/sandwiches?pool=0x4538053f…&limit=20" -H "X-API-Key: pmk_…" | jq '.[0]'

Incremental polling. Pass sinceBlock (or envelope=1) and the response becomes an envelope carrying the scan head. Feed lastBlock back as the next sinceBlock and you never miss or re-download anything:

# first call: everything after block N (empty items is normal)
curl -s "https://mev.parascan.dev/api/sandwiches?sinceBlock=87173000"
{
  "items": [ /* sandwiches with blocks[1] > 87173000 */ ],
  "count": 2,
  "lastBlock": 87173695,   // ← pass this as sinceBlock next poll
  "totalOnRecord": 493
}
GET/api/risk?pool=0x…

Toxicity of a pool or token right now — built for execution agents deciding slippage before a trade. Query by pool (address) or token (address, native, or symbol); one of the two is required.

Protocol teams: the dashboard's “Watch your pool” box calls this endpoint — paste a pool or token address there for a no-code view of the same data.

curl -s "https://mev.parascan.dev/api/risk?token=WMON" | jq
{
  "query": { "token": "WMON" },
  "risk": "elevated",        // none | low | elevated | high
  "hint": "sandwiched within the last 24h — set a tight slippage limit before trading",
  "lastBlock": 87173695,
  "allTime":  { "sandwiches": 61, "victims": 64, "extractedUsd": 48.02, "attackers": 12 },
  "last24h":  { "sandwiches": 2,  "victims": 2,  "extractedUsd": 1.71,  "attackers": 1 },
  "lastSandwich": { "block": 87171502, "agoBlocks": 2193, "at": 1783819771000, "venue": "v3", "pool": "0x4538…", "attacker": "0x22c6…" },
  "activeBots7d": [ { "attacker": "0x22c6…", "sandwiches": 9, "lastBlock": 87171502 } ],
  "pools": [ /* pools where the token was sandwiched (token queries) */ ],
  "trackedPools": 17
}

Risk levels: high — a sandwich in the last hour, or ≥3 in 24h; elevated — at least one in 24h; low — sandwiched historically, quiet for 24h; none — never recorded. Pool queries also return poolStats (kind, lifetime swaps and sandwiches).

GET/api/arbs?limit=30
ParamDescription
limitnumber of rows, default 30, max 100 (newest first)

Atomic-arb candidates: single transactions that swapped through ≥2 pools. Unlike sandwiches, these are not confirmed for profit.

[
  { "tx": "0x1bd82533…", "pools": 3, "block": 87173098, "at": 1783820646088 }
]
GET/healthz

Watcher liveness. status is ok, starting, or degraded (RPC trouble; lastError says what). Not rate-limited.

{ "status": "ok", "lastBlock": 87173695, "lastError": null }

Object shapes

Sandwich — one confirmed attack:

FieldDescription
venuev2 | v3 | v4 | kuru | pcs3 (PancakeSwap v3) | lfj (LFJ Liquidity Book) — PancakeSwap v2 pools emit the canonical v2 event and land in v2
viaconfirmation rule: sender (same EOA front+back), bot-contract (same executor contract), txOrigin (Kuru)
poolpool address; v4 uses manager:poolId; Kuru is the orderbook address
attackerattacking origin address
botexecutor contract, when confirmed via bot-contract
victim / victimsfirst victim address / count of victim fills bracketed
blocks[open, close] block numbers
front, victimTx, backthe three transaction hashes
attimestamp (ms) — live detection time, or estimated block time for backfill
backfilltrue when found by historical replay rather than live
profitsee below

profit — pool-leg accounting, marked to the victim's own execution price:

FieldDescription
usdgross extracted USD (can be negative — bots misfire; shown as-is), or null when unpriceable
gasUsdattacker gas for front+back (exact, from receipts)
netUsdusd − gasUsd
token, amountthe leftover leg the figure was priced from
pricedfalsereason explains: unpriced-pair, kuru-unverifiable-book, price-sanity, implausible-usd, rpc-failed, no-swaps

Errors

HTTP 429 — over the rate limit (Retry-After: 30)
{
  "error": "rate limit exceeded",
  "limit": "30/min",
  "hint": "pass an API key via the X-API-Key header for a higher limit — request one at github.com/krimdev/paramev/issues"
}

Anything else non-200: 404 for unknown paths, 405 for non-GET methods. The API never returns partial data silently — if the watcher is degraded you'll see it in lastError on /api/summary and /healthz.