blockmetric (Python)
Official Python client of the Blockmetric API for real-time crypto market data: liquidation maps and heatmaps from real Hyperliquid on-chain positions, whales, order flow, plus funding, open interest and liquidation totals from the exchanges whose data the API may redistribute. Standard library only, Python 3.9+, fully typed (TypedDicts generated from the OpenAPI document).
Not on PyPI yet. Until it is published, install it from this repository:
pip install -e sdk/python(or putsdk/pythononPYTHONPATH). Once published:pip install blockmetric.
60-second quickstart
-
Create a key on /account/api (every account has a personal developer key; the full key is shown once).
-
Set it (and, until the SDK ships with the production URL baked in, the base URL):
export BLOCKMETRIC_API_KEY=bm_live_... export BLOCKMETRIC_BASE_URL=https://<domain>/api/v1
-
First request:
from blockmetric import Blockmetric client = Blockmetric() # reads BLOCKMETRIC_API_KEY and BLOCKMETRIC_BASE_URL res = client.liquidation_summary(hours=24) s = res["data"] print(f"24h liquidations: ${s['totalUsd']:,.0f} (longs ${s['longUsd']:,.0f}, shorts ${s['shortUsd']:,.0f})") print(res.last_response.rate_limit.remaining, "requests left this minute")
Every method returns the API envelope {"data": ..., "meta": ...} as a plain dict (JSON-serialisable), typed as the
matching TypedDict (blockmetric.types.LiquidationSummaryResponse, …). The HTTP details are attached as
res.last_response: status, request_id, rate_limit (limit, remaining, reset per minute; limit_day,
remaining_day, reset_day and overage when the server sends them), etag, not_modified, deprecation, sunset,
api_version, key_expires_at, headers. client.last_response holds those of the most recent request.
Endpoints
| Method | API | |
|---|---|---|
markets(venue=, fields=, format=) |
GET /markets |
Market state of a venue |
funding(coin=, limit=, fields=, format=) |
GET /funding |
Cross-venue funding board |
coins(fields=, format=) |
GET /coins |
Hyperliquid coins |
long_short(coin=, limit=, fields=, format=) |
GET /long-short |
Long/short positioning |
liquidations(coin=, venue=, side=, min_usd=, since_minutes=, from_=, to=, after=, limit=, fields=, format=) |
GET /liquidations (paginated) |
Liquidations feed |
liquidation_summary(coin=, hours=) |
GET /liquidations/summary |
Liquidation totals |
liqmap(coin) |
GET /liqmap/{coin} |
Liquidation map (current) |
candles(coin, tf=, from_=, to=, limit=, fields=, format=) |
GET /candles/{coin} |
Price candles (OHLC) |
heatmap(coin, tf=, hours=, end=, columns=, clusters=, venue=, step_minutes=, layer=) |
GET /heatmap/{coin} |
Liquidation heatmap over time |
heatmap_open_interest(coin, hours=, end=, venue=, band_pct=) |
GET /heatmap/{coin}/open-interest |
Open interest added per price band |
heatmap_wallets(coin, lo=, hi=, price=, clusters=, side=, min_usd=, limit=) |
GET /heatmap/{coin}/wallets |
Wallets with their liquidation price in a band |
vaults(sort=, min_tvl=, closed=, q=, limit=) |
GET /vaults |
Vault leaderboard |
hyperliquid_protocol(days=, recent=) |
GET /hyperliquid/protocol |
Hyperliquid protocol state |
correlation(coins=, limit=) |
GET /correlation |
Correlation and beta to BTC |
scoreboard(coin=, limit=) |
GET /scoreboard |
Cluster hit rates (scoreboard) |
top_positions(coin=, side=, min_usd=, limit=, venue=, fields=, format=) |
GET /positions/top (paginated) |
Largest positions |
whales(sort=, coin=, labelled=, limit=, venue=, fields=, format=) |
GET /whales |
Whale leaderboard |
whale_feed(coin=, min_usd=, since=, limit=, fields=, format=) |
GET /whales/feed (paginated) |
Whale position changes |
wallet(address, limit=) |
GET /wallets/{address} |
Wallet detail |
whale_stops(coin=) |
GET /whales/stops |
Whale stop / TP map |
traders(sort=, search=, style=, min_account_value=, limit=, fields=, format=) |
GET /traders (paginated) |
Trader leaderboard |
cohorts(coin=, dim=, cohort=, limit=) |
GET /cohorts |
Cohort positioning |
smart_money(coin=, limit=) |
GET /smart-money |
Smart-money convergence |
orderbook(coin, venue=, hours=) |
GET /orderbook/{coin} |
Order book liquidity and walls |
flow(coin, venue=, hours=, step_minutes=) |
GET /flow/{coin} |
Taker flow (CVD) and volume profile |
tape(coin=, venue=, side=, min_usd=, since_minutes=, from_=, to=, after=, limit=, fields=, format=) |
GET /tape (paginated) |
Large-trade tape |
screener(coin=, sort=, dir=, min_oi_usd=, limit=, fields=, format=) |
GET /screener |
Screener (every coin) |
predicted_funding(coin=, limit=, fields=, format=) |
GET /funding/predicted |
Predicted next funding |
funding_spreads(coin=, basis=, min_oi_usd=, limit=, fields=, format=) |
GET /funding/spreads |
Cross-venue funding spreads |
sentiment(days=) |
GET /sentiment |
Blockmetric Sentiment index |
radar_events(kinds=, min_severity=, coin=, since=, limit=, fields=, format=) |
GET /radar/events (paginated) |
Risk radar events |
security_incidents(origin=, type=, chain=, min_severity=, limit=, fields=, format=) |
GET /security/incidents (paginated) |
Security incidents |
security_wallets(category=, network=, source=, q=, moved_days=, limit=, fields=, format=) |
GET /security/wallets (paginated) |
Listed wallets (OFAC, FBI, post-mortems) |
security_wallet(address, limit=) |
GET /security/wallets/{address} |
Listed wallet detail |
movements(type=, chain=, asset=, min_usd=, labelled=, address=, since=, limit=, fields=, format=) |
GET /movements (paginated) |
Whale movements (on-chain + Hyperliquid) |
exchange_flows(hours=, exchange=, asset=) |
GET /exchange-flows |
Exchange on-chain flows |
stats() |
GET /stats |
Coverage and freshness |
me() |
GET /me |
The calling key: plan, limits, usage |
status() |
GET /status (public, no key) |
API status (public) |
stream(channel=, kinds=, min_severity=, venue=, min_usd=, coin=, last_event_id=) |
GET /stream (API Pro, Server-Sent Events) |
Live event stream (SSE) |
Parameters are keyword-only; snake_case (since_minutes) and the API's camelCase (sinceMinutes) both work, an
unknown one raises TypeError before any request. Lists become comma-separated values (kinds=["hack", "depeg"]),
booleans true / false. Units: times ISO 8601 UTC, money in USD, funding rates, distances and changes as fractions
(0.01 = 1 %).
Liquidation heatmap
from blockmetric import Blockmetric, heatmap_clusters
hm = client.heatmap("BTC", tf="15m", hours=24)["data"]
c = heatmap_clusters(hm, top=3)
for x in c["above"]:
print(f"shorts liquidate at {x['price']:,.0f} (+{x['distance']:.1%}): ${x['usd']:,.0f}")
for x in c["below"]:
print(f"longs liquidate at {x['price']:,.0f} ({x['distance']:.1%}): ${x['usd']:,.0f}")
tf is the column interval (1m · 5m · 15m · 1h · 4h · 12h · 1d, as your plan allows), end= pages back in time.
Pagination
for liq in client.paginate("liquidations", since_minutes=24 * 60, coin="BTC", limit=1000):
print(liq["ts"], liq["venue"], liq["side"], liq["usd"])
paginate follows meta.nextCursor until the last page (every operation marked "paginated" above); max_pages=
stops earlier. pages() yields the full envelope of each page instead of the items.
New items as they arrive (any key)
import threading
stop = threading.Event() # stop.set() from another thread ends the loop
for liq in client.follow("liquidations", coin="BTC", limit=200, interval=15, stop_event=stop):
if (liq["usd"] or 0) >= 100_000:
print("big liquidation", liq["side"], liq["usd"], liq["price"])
follow polls a feed (liquidations, whale_feed, radar_events, tape, movements), reads back until the newest
item it has already seen and yields every new item once, oldest first. Retryable errors go to on_error and the loop
continues. Mind your per-minute limit when choosing interval.
Live stream (API Pro)
for event in client.stream(kinds=["big_liquidation", "liq_cascade"], min_severity=2, max_duration=3600):
print(event["type"], event["data"]["severity"], event["data"]["title"])
Each item is the webhook envelope {"id": "evt_…", "type", "created", "data"}. The client reconnects on its own
(honouring the server's retry:, also after idle_timeout seconds without the 15 s heartbeat) and resumes with
Last-Event-ID, so nothing is lost inside the server's 15-minute resume window. It stops on break, after
max_duration seconds or when stop_event (a threading.Event) is set (also from another thread); the open
connection is closed right away. on_ready, on_gap (events outside the window: backfill with radar_events()) and
on_reconnect are optional callbacks. Without API Pro, follow("radar_events") gives the same events by polling.
Webhooks (API Dev, API Pro)
Verify the raw body exactly as received, never re-serialised JSON:
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
from blockmetric import construct_event, BlockmetricError
SECRET = os.environ["BLOCKMETRIC_WEBHOOK_SECRET"] # whsec_..., shown once when you create the endpoint
class Hook(BaseHTTPRequestHandler):
def do_POST(self):
raw = self.rfile.read(int(self.headers.get("Content-Length", 0)))
try:
event = construct_event(raw, self.headers.get("Blockmetric-Signature"), SECRET)
except BlockmetricError:
self.send_response(400)
self.end_headers()
return
self.send_response(204) # answer within 10 s, then work
self.end_headers()
print(event["id"], event["type"], event["data"]["title"]) # deduplicate on event["id"] (retries reuse it)
HTTPServer(("", 8080), Hook).serve_forever()
verify_webhook(body, header, secret, tolerance=300) returns a bool instead (constant-time comparison, any v1
matches: during a secret rotation the old secret keeps working for 24 h). sign_webhook(body, secret) produces a
header for your own tests.
Errors and retries
Every failure raises blockmetric.BlockmetricError with code (see the error catalogue at doc_url), status,
param (the parameter it is about, when the API names one), request_id (quote it to support), retry_after,
retryable and message.
from blockmetric import Blockmetric, BlockmetricError
try:
Blockmetric().heatmap("SOL", tf="15m")
except BlockmetricError as e:
if e.code == "plan_required":
print("needs a higher plan:", e.message)
elif e.code == "source_restricted":
print("not redistributed through the API:", e.message)
else:
raise
429, 5xx, timeouts and network errors are retried (max_retries=3) with exponential backoff and jitter, honouring
Retry-After (seconds or an HTTP date) up to max_retry_delay=60 seconds; a longer wait (the daily limit) raises
immediately. etag_cache=True keeps the last body per URL and revalidates with If-None-Match (a 304 still counts
against the limit but transfers no body). user_agent="my-app/1.0" names your app in front of the SDK's User-Agent,
on_retry and on_warning (every meta.warnings entry) are hooks for logging.
Plans and limits
| Access | Requests | History | Licence | Realtime |
|---|---|---|---|---|
No key (X-Blockmetric-Access: public) |
10 / min, 300 / day per IP address | 24 h | public (non-commercial) | - |
| Free | 30 / min, 1,000 / day | 30 days | personal use | - |
| Pro | 40 / min, 5,000 / day | 1 year | personal use | - |
| Desk | 60 / min, 10,000 / day | 3 years | personal use | - |
| API Dev | 60 / min, 50,000 / day | 1 year | internal use (1 organisation) | webhooks |
| API Pro | 300 / min, 400,000 / day | 3 years | display in your product | live stream, webhooks |
Limits apply per account across all its keys; every plan is real time. Every key gets the heatmap of every coin: free keys the last 24 hours, paid plans their plan history. Every response names its licence (meta.license) and the credit to show (meta.attribution). Current prices: /pricing on the site. Data from public sources, without guarantee.
Versioning
The package follows semantic versioning (blockmetric.__version__) and is generated against one API contract
(blockmetric.API_VERSION, sent in the User-Agent). Within /api/v1 the API only adds fields and parameters, so an
older SDK keeps working; last_response.api_version shows the contract that answered. Changes: sdk/CHANGELOG.md.
Licence
The package is licensed under the Apache License 2.0 (see also NOTICE). The licence covers the
code, not the name: "Blockmetric" and the logo are not licensed, so forks must be renamed; "works with Blockmetric" is
fine (TRADEMARKS.md). Data you receive from the API is governed by the API terms
(/terms#api-license on the site; meta.license in every response); market data of third-party exchanges stays subject
to that exchange's terms (/terms#third-party-data). The examples in examples/ and the code snippets in the API docs
are MIT-0 (copy them into your own code without attribution). No warranty for the data.
Development
python -m unittest discover -s sdk/python/tests -v # local fake API, no network
npx tsx scripts/devx-generate.ts # regenerates blockmetric/types.py, _config.py and this README's tables
Publishing is a manual operator step: set SITE_URL to the production https URL, run
npx tsx scripts/devx-generate.ts --release, remove the Private :: Do Not Upload classifier from pyproject.toml,
check the copyright holder in NOTICE (exact registered company name), then build (setuptools ≥ 77.0.3, PEP 639
licence metadata) and upload. Examples: examples/.
Metadata
Release files for blockmetric 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| blockmetric-0.3.0.tar.gz | 72.3 kB | Without provenance | Details for blockmetric-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| blockmetric-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for blockmetric-0.3.0-py3-none-any.whl |
Total release size: 129.9 kB
Release files / blockmetric-0.3.0.tar.gz
| Download URL | blockmetric-0.3.0.tar.gz |
|---|---|
| Size | 72.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ef401668d54358a5f61d6555c3a93e76d1ff67b24ba8cb8aa8415dab7b19c6a3
|
|
BLAKE2b-256 checksum How to use checksums |
a0ea6c16f93afe5f5420cb76ca38a4673c7e96ae750e2799c43b75cc907c3756
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / blockmetric-0.3.0-py3-none-any.whl
| Download URL | blockmetric-0.3.0-py3-none-any.whl |
|---|---|
| Size | 57.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cf34ec0630afdf1abdb7a7073e66da96d0dbc591d2d4dc42f9b1fdee4ca7e4e3
|
|
BLAKE2b-256 checksum How to use checksums |
9537c79af5e72dbd5da499691e643b6cd6f41b781f282599c90356c291fcdb60
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Security for blockmetric 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.