REST + MCP reference for AI agents. Bitpoort exposes 80 MCP tools and 328 REST paths (364 OpenAPI operations) live across Ethereum, Bitcoin, and Hyperliquid. Most surfaces are public and machine-readable — every example below is a real call you can paste.
Three calls, no signup. The key endpoint is public; paste the returned bp_ key into the two calls that follow.
# 1 — free key, no signup (50 keys/IP/day, 90-day expiry)
curl -sX POST https://api.bitpoort.com/v1/keys
# 2 — first data call
curl -H "X-API-Key: bp_..." "https://api.bitpoort.com/v1/whales/recent?chain=ETH&limit=3"
# 3 — screen an address against OFAC / UK HMT / IL NBCTF / JP METI
curl -X POST https://api.bitpoort.com/v1/forensic/screen -H "X-API-Key: bp_..." -H "Content-Type: application/json" -d '{"address":"0x098B716B8Aaf21512996dC57EB0615e2383E2f96","chain":"ETH"}'
Step 3 returns a live sanctions match on a designated Lazarus Group address (abridged — timestamps are ISO-8601 with microseconds and an explicit offset):
{
"address": "0x098b716b8aaf21512996dc57eb0615e2383e2f96",
"chain": "ETH",
"sanctions_status": "MATCH",
"screening_status": "MATCH",
"match_count": 2,
"matches": [
{ "source": "OFAC_SDN", "list_id": "27307",
"entity_name": "LAZARUS GROUP", "confidence": 1.0,
"last_verified_at": "2026-08-20T03:01:07.332962+00:00" },
{ "source": "JP_METI", "confidence": 1.0, "metadata": {
"aggregator": "OpenSanctions", "designating_authority": "JP_METI" } }
],
"disclaimer": "Not legal advice. Verify with compliance counsel.",
"_meta": { "tool_version": "1.2",
"sources": ["IL_NBCTF", "JP_METI", "OFAC_SDN", "UK_HMT"] }
}
Note the address is echoed lower-cased, and matches can carry several designations for one address — count them from match_count, not from the presence of the array.
To plug the same surface into an agent, point your MCP client at https://mcp.bitpoort.com/ with an X-API-Key header — paste-ready configs for every supported host are at /agents.md.
https://api.bitpoort.com — 328 REST paths (364 OpenAPI operations), of which 81 operations are public (no key).security: [] per-route, so you can filter the spec for exactly what works without a key.X-API-Key: bp_... (alternative: Authorization: Bearer bp_...).chain, limit, min_usd, since, until, and an opaque cursor.Two response fields are contract but are not described in the OpenAPI schema — read them here.
List and chain endpoints attach the freshness of the pipeline that served them. Treat data_fresh: false or a non-empty warnings array as a reason to caveat the answer, not to retry.
// healthy
"coverage_status": { "chain": "ETH", "tier": "live", "data_fresh": true,
"pipeline": "full", "node": "erigon-archive", "warnings": [] }
// degraded — the SHAPE returned when a chain's indexing pipeline trails
// its node. Illustrative: same chain as above, a different state.
"coverage_status": { "chain": "ETH", "tier": "stale", "data_fresh": false,
"pipeline": "pending", "node": "erigon-archive",
"warnings": ["... indexing pipeline is pending ..."] }
tier is one of live, stale, node-only, perps-only, unknown. pipeline is one of full, perps-only, pending, mixed, unknown. An unrecognised chain yields tier: "unknown" with an explanatory warning rather than an error — so always read the block, never assume a 200 means fresh data.
Paginated endpoints return next_cursor and has_more alongside the rows. Pass the opaque next_cursor value back as ?cursor= to fetch the next page; stop when has_more is false (next_cursor is then null). Cursors encode sort position, so they stay stable while new blocks land — never construct one by hand.
Analytical responses carry _meta.sources (which registers were searched, or which pipelines produced the answer — not merely the ones that matched; per-match attribution is in matches[].source) and _meta.tool_version. Provenance is part of the answer: if a response cannot say where a number came from, treat it as unattributed.
Three different envelopes ship, and a 404 can be two of them. Handle all three — matching only on the nested shape is the most common integration bug.
# A. nested "error" object — 401, 403, 422, 429
{"error":{"code":"AUTH_REQUIRED",
"message":"API key required. Get a free key instantly: curl -X POST https://api.bitpoort.com/v1/keys",
"docs":"https://docs.bitpoort.com"}}
# 422 uses the same shape and adds a "details" array:
{"error":{"code":"VALIDATION_ERROR",
"message":"limit: Input should be a valid integer, ...",
"hint":"Check the parameter names and value ranges. ...",
"docs":"https://docs.bitpoort.com",
"details":[{"type":"int_parsing","loc":["query","limit"], ...}]}}
# B. flat — 404 on an unknown PATH
{"error":"NOT_FOUND","path":"/v1/token-flows","method":"GET",
"hint":"This path doesn't exist. Browse the full path list at .../openapi.json"}
# C. wrapped in "detail" — 404 on an unknown or inapplicable CHAIN
{"detail":{"error":"UNKNOWN_CHAIN","received":"NOPE",
"valid_chains":["ARB","AVAX","BASE","BTC","ETH","MATIC","OP","SOL"],
"hint":"'NOPE' is not a valid chain ID. Valid: ..."}}
| Status | Code | Meaning |
|---|---|---|
| 401 | AUTH_REQUIRED | No key sent on an authenticated path. |
| 401 | AUTH_INVALID | Key malformed, revoked, or past its 90-day expiry. |
| 403 | — | Valid key, but the route needs a higher tier. |
| 422 | VALIDATION_ERROR | Bad parameter. Nested shape plus a details array naming the offending field. |
| 404 | NOT_FOUND | Unknown path. Flat shape (B); hint names the entry points. |
| 404 | UNKNOWN_CHAIN | Chain ID not recognised. detail-wrapped (C); carries valid_chains. |
| 404 | CHAIN_NO_BLOCKS | Valid chain with no EVM blocks (Hyperliquid). Shape C; the hint names the tools to use instead. |
| 400 | INVALID_CURSOR | Cursor malformed or expired. Shape C; drop cursor and re-paginate from the start. |
| 429 | — | Rate limit. See X-RateLimit-Reset (unix seconds) before retrying. |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (unix seconds). These report the edge window currently applied to the caller, which is not the same number as your key's per-minute tier quota — that one is returned by POST /v1/keys as rate_limit. Back off on Remaining: 0 rather than polling.
https://mcp.bitpoort.com/ — Streamable HTTP, JSON-RPC over POST.X-API-Key header as REST. A free key reaches all 80 MCP tools.network_status, recent_whales, emerging_tokens) resolve transparently to canonical tool names, so a mis-remembered name still works.List the live catalog without installing anything. Note the Accept header — Streamable HTTP requires both media types, and omitting it is the most common setup failure:
curl -X POST https://mcp.bitpoort.com/ -H "X-API-Key: bp_..." -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Names and JSON schemas come from tools/list on the MCP endpoint; the same catalog with usage notes and per-lane counts is at /agents.md and, machine-readable, in /.well-known/mcp.json. What each lane is for:
An empty result is classified rather than returned bare, so an agent can tell “we searched and found nothing” apart from “we could not search.” Results carry is_successful_empty plus an empty_reason drawn from a fixed set.
Branch on the flag, not on the reason. is_successful_empty is authoritative: true means the lookup completed and the answer is genuinely nothing, false means the result is not usable as evidence, and null means the reason could not be established. A tool that knows better sets it explicitly; anything classified defaults to true, and unclassified always yields null. The reason tells you why:
| empty_reason | Read it as |
|---|---|
no_signal | Checked to completion; nothing matched. |
no_sanctions_match | Screened the sanctions registers; address is not listed. Negative evidence. |
no_abuse_match | Screened community abuse lists; no report. Negative evidence. |
no_mixer_match | No mixer interaction found. Negative evidence. |
unsupported_input | Input out of scope for this tool (wrong chain, wrong address type). |
stale_pipeline | The backing pipeline is behind; the silence is not meaningful. |
data_gap | Coverage genuinely missing for that range. Do not read as “clean.” |
unclassified | Empty, reason not established (is_successful_empty is null). Treat as inconclusive. |
The three compliance reasons are deliberately distinct from no_signal: for a compliance officer, “we searched this register and the address is not on it” is a finding worth recording, and the headline names the register that was searched.
Send X-API-Key: bp_... (or Authorization: Bearer bp_...). Free keys are issued instantly by POST /v1/keys, capped at 50 per IP per day, and expire after 90 days. The live figures below are served by /v1/billing/tiers (public) — note Compliance is tuned for signed-report workloads, not request volume, so its throughput is lower than Pro.
| Tier | Price | Rate | Daily | Adds |
|---|---|---|---|---|
| Free | — | 60/min | 10,000 | All 80 MCP tools, 90-day key |
| Pro | $99/mo | 600/min | 1,000,000 | Managed chat, OAuth, dashboards, named keys |
| Compliance | $1,000/mo | 100/min | 100,000 | Signed reports + continuous monitoring |
| Enterprise | custom | 5,000/min | uncapped | Sales-led, not self-serve |
81 REST operations need no key at all, including POST /v1/keys, /health, /v1/health, /v1/trust, /v1/whales/recent, /v1/feed/emerging-tokens, /v1/verify/{content_hash}, /openapi.json, /openapi-summary.json, and /redoc. The complete list ships in /openapi-summary.json.
| Chain | Param | Status | Backing |
|---|---|---|---|
| Ethereum | ETH | live | Own Erigon archive node, full pipeline |
| Bitcoin | BTC | live | Own Bitcoin Core node, full pipeline |
| Hyperliquid | HL | perps-only | Perps market, traders, funding. No EVM blocks — /v1/blocks/* returns 404 CHAIN_NO_BLOCKS; use the get_hl_* tools or /v1/feed/hl/* |
| Aggregate | ALL | live | Combines ETH + BTC + HL |
Trap worth knowing: the block endpoints also accept ARB, AVAX, BASE, OP, and SOL as valid chain IDs, but no pipeline backs them — they return 200 with a null block and coverage_status.tier: "unknown", not an error. Check coverage_status rather than the HTTP status.
Sanctions screening is chain-agnostic and covers designations across BTC, ETH, TRON, LTC, XMR and others — the per-chain breakdown is live in /v1/trust.
attestation.signature (HMAC-SHA256), the git_commit and alembic_head that produced it, the shared eval_baseline, and data_provenance counts pinned at generation time — so a report stays checkable against the exact build that made it.GET https://api.bitpoort.com/v1/verify/<content_hash> returns the exact signed JSON plus a server-side signature re-check. Public, no auth. Append /canonical for the canonical bytes or /pdf for the signed PDF re-render.