AI agent with live market data

An assistant that answers with live liquidations, clusters, funding and whale moves, and cites them.

Cheapest plan
No key
Time to build
10 min
Endpoints
5
/llms.txt · /heatmap/{coin} · /liquidations/summary
Languages
2
Node 18+, Python 3.9+, no installs

What you get

Give a language model four tools (liquidation clusters, liquidation totals, funding, whale moves) with JSON-Schema definitions any tool-calling API accepts, plus /llms.txt as context. Answers come trimmed to what a model needs, with the time of the data and the credit line, so it cites correctly.

liquidation_clusters(coin)
liquidations_summary(coin, hours)
funding(coin)
whale_moves(coin, min_usd, limit)

liquidation_clusters({"coin":"BTC"}) -> {"coin":"BTC","mark":81486,"above":[{"price":91500,"usd":104183604,"distance":0.1229},{"price":97500,"usd":92616070,"distance":0.1965},{"price":89500,"usd":70949011,"distance":0.0…
liquidations_summary({"hours":24}) -> {"hours":24,"totalUsd":106495945.77,"longUsd":14368941.56,"shortUsd":92127004.22,"count":33659,"byVenue":[{"venue":"binance","longUsd":11931819.87,"shortUsd":59750027.18,"count":265…
funding({"coin":"ETH"}) -> {"coin":"ETH","byVenue":{"hyperliquid":0.1095},"aprSpread":null,"updatedAt":"2026-10-28T09:53:00.912Z","credit":"Data by Blockmetric · Source: Hyperliquid"}
whale_moves({"limit":3}) -> {"moves":[{"ts":"2026-10-28T09:51:11.745Z","wallet":"0xf71ab4e4172730ed096b832f37982942935c7215","coin":"ETH","kind":"close","side":"short","usdDelta":-1044746,"price":2371.6},{"ts":"2026-10-…

Real output of the program below, run against our API on 2026-10-28 10:38 UTC.

Build it step by step

  1. Give the model the map

    Put /llms.txt into the system prompt: every endpoint, the auth header, units and limits on one page. /llms-full.txt is the full reference when the model needs field details.

    curl -s "$SITE/llms.txt"
  2. Register the tools

    Pass tools (name, description, parameters as JSON Schema) to your model. Some SDKs call the schema field input_schema; rename it there.

  3. Run the calls

    When the model asks for a tool, run callTool(name, args) / call_tool(name, args) and send the result back. Errors are returned as { error, message } instead of exceptions.

  4. Let it cite

    Every result carries updatedAt and credit. Ask the model to name the time and the source in its answer.

The complete tool set

Set this once in the shell that runs the code:

export BLOCKMETRIC_API_KEY=bm_live_…   # create one on /account/api
export BLOCKMETRIC_BASE_URL=https://api.blockmetric.io/v1
export SITE=https://blockmetric.io
// Tools for an AI agent: four Blockmetric functions with JSON-Schema definitions that any tool-calling LLM API accepts
// (pass `tools` to your model, run `callTool(name, args)` when it asks, send the result back). Node 18+, no dependencies.
// The answers are trimmed to what a model needs, with the time of the data and the credit line, so it cites correctly.
//
//   export BLOCKMETRIC_API_KEY=bm_live_...            # optional: without a key the tools use keyless public access
//   export BLOCKMETRIC_BASE_URL=https://<domain>/api/v1
//   node agent-tools.mjs                               # demo: prints every tool and one call of each
//
// Context for the model: put the text of /llms.txt into the system prompt; it lists every endpoint in one page.
import { pathToFileURL } from 'node:url'

const BASE = (process.env.BLOCKMETRIC_BASE_URL ?? '').replace(/\/+$/, '')
const KEY = process.env.BLOCKMETRIC_API_KEY
if (!BASE) throw new Error('Set BLOCKMETRIC_BASE_URL, e.g. https://<domain>/api/v1')

async function api(path, params = {}) {
  const url = new URL(BASE + path)
  for (const [k, v] of Object.entries(params)) if (v !== undefined && v !== null && v !== '') url.searchParams.set(k, String(v))
  const res = await fetch(url, { headers: KEY ? { Authorization: `Bearer ${KEY}` } : { 'X-Blockmetric-Access': 'public' }, signal: AbortSignal.timeout(30_000) })
  const body = await res.json()
  // Errors go back to the model as data: code and message tell it how to correct the call.
  if (!res.ok) return { error: body.error?.code, message: body.error?.message }
  return body
}
const meta = (b) => ({ updatedAt: b.meta.updatedAt, credit: b.meta.attribution.text })

/** Tool definitions (JSON Schema). Some SDKs call the schema field input_schema instead of parameters. */
export const tools = [
  {
    name: 'liquidation_clusters',
    description: 'Where liquidations would happen next for one coin: the largest clusters of liquidation prices above and below the current price, from real tracked Hyperliquid positions. Distance is a fraction of the price (0.02 = 2 %).',
    parameters: { type: 'object', properties: { coin: { type: 'string', description: 'Coin, e.g. BTC, ETH, HYPE' } }, required: ['coin'] },
  },
  {
    name: 'liquidations_summary',
    description: 'Liquidations that happened: long and short USD, count, per venue and the largest single one over the last hours.',
    parameters: { type: 'object', properties: { coin: { type: 'string' }, hours: { type: 'integer', minimum: 1, maximum: 168, default: 24 } } },
  },
  {
    name: 'funding',
    description: 'Funding rates of one coin on every venue (annualised fractions, 0.10 = 10 % a year) and the spread between them.',
    parameters: { type: 'object', properties: { coin: { type: 'string' } }, required: ['coin'] },
  },
  {
    name: 'whale_moves',
    description: 'Recent position changes of large tracked Hyperliquid wallets (open, add, cut, close, flip, liquidated), newest first.',
    parameters: { type: 'object', properties: { coin: { type: 'string' }, min_usd: { type: 'number', default: 1000000 }, limit: { type: 'integer', default: 10, maximum: 50 } } },
  },
]

export async function callTool(name, args = {}) {
  if (name === 'liquidation_clusters') {
    const b = await api(`/heatmap/${encodeURIComponent(args.coin)}`, { tf: '15m', hours: 1, clusters: 5 })
    if (b.error) return b
    const c = b.data.clusters
    const pick = (x) => ({ price: x.price, usd: Math.round(x.usd), distance: Number(x.distance.toFixed(4)) })
    return { coin: b.data.coin, mark: c?.mark ?? null, above: c?.above.map(pick) ?? [], below: c?.below.map(pick) ?? [], ...meta(b) }
  }
  if (name === 'liquidations_summary') {
    const b = await api('/liquidations/summary', { coin: args.coin, hours: args.hours ?? 24 })
    if (b.error) return b
    const { totalUsd, longUsd, shortUsd, count, byVenue, largest } = b.data
    return { hours: b.data.hours, totalUsd, longUsd, shortUsd, count, byVenue, largest, ...meta(b) }
  }
  if (name === 'funding') {
    const b = await api('/funding', { coin: args.coin })
    if (b.error) return b
    const r = b.data[0]
    if (!r) return { error: 'not_found', message: `No funding for ${args.coin}` }
    return { coin: r.coin, byVenue: Object.fromEntries(Object.entries(r.byVenue).map(([v, x]) => [v, x?.fundingApr ?? null])), aprSpread: r.aprSpread, ...meta(b) }
  }
  if (name === 'whale_moves') {
    const b = await api('/whales/feed', { coin: args.coin, minUsd: args.min_usd ?? 1_000_000, limit: Math.min(args.limit ?? 10, 50) })
    if (b.error) return b
    return { moves: b.data.map((e) => ({ ts: e.ts, wallet: e.label ?? e.address, coin: e.coin, kind: e.kind, side: e.side, usdDelta: Math.round(e.usdDelta), price: e.price })), ...meta(b) }
  }
  return { error: 'unknown_tool', message: `No tool named ${name}` }
}

// Demo: what the model would receive for one call of each tool.
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
  for (const t of tools) console.log(`${t.name}(${Object.keys(t.parameters.properties).join(', ')})`)
  console.log('')
  for (const [name, args] of [['liquidation_clusters', { coin: 'BTC' }], ['liquidations_summary', { hours: 24 }], ['funding', { coin: 'ETH' }], ['whale_moves', { limit: 3 }]])
    console.log(`${name}(${JSON.stringify(args)}) ->`, JSON.stringify(await callTool(name, args)))
}

Run it:

node agent-tools.mjs   # prints every tool and one call of each
python agent_tools.py

Download: agent-tools.mjs · agent_tools.py · requests.sh. MIT-0: use it as you like.

Rate limits and pagination

  • One tool call is one API request. An agent that calls three tools per question uses three requests.
  • Keyless access is limited per IP (10/min); errors come back as data, so the model can explain or retry.
  • Trim answers before they reach the model: the heatmap tool returns clusters, not the whole grid.
  • Keep the key in the tool runner, never in the prompt.
  • OpenAPI 3.1 (/api/v1/openapi.json) works for frameworks that build tools from a spec.

Common pitfalls

  • Models invent parameters. The tool descriptions name the units (fractions, USD) and the API answers invalid_param with the allowed values.
  • A model that loops on one tool burns requests; cap tool calls per question.

Who it is for

  • Builders of trading assistants and research agents
  • Teams adding a chat interface to market data
  • Anyone who wants an LLM to call the API correctly the first time

Plan and limits

No keyCheapest
Works without a key: 10/min and 300/day per IP, 24 h of history.
10/min and 300/day per IP, 24 h of history
Free
A free personal key: 10/min, 1,000/day, 30 days of history.
10/min, 1,000/day, 30 days of history
API Dev$49/mo
An agent for your team or organisation, more requests.
60/min, 50,000/day, 365 days of history
API Pro$249/mo
An agent in your product that shows the data to your users.
300/min, 500,000/day, 1095 days of history

Compare plans

Honest limits

  • Data from public sources, not investment advice; tell the model to say so.
  • The MCP server is being prepared for release; until then these tools give an agent the same data through the REST API.

Next steps

More recipes like this

LiveHyperliquid markets 19sOperationalHyperliquid positions & whales 6sOperationalLiquidation heatmap & levels 8sOperationalLighter markets & liquidations 23sOperationalLighter positions & heatmap 10sOperationalLighter RH positions & heatmap 8sOperationalBinance markets 52sOperationalBybit markets 12sOperationalBinance liquidations 29sOperationalBybit liquidations 29sOperationaldYdX positions & heatmap 11sOperationalGMX positions & heatmap 17sOperationalLong/short & open interest history 26sOperationalOrder books & trade flow 6sOperationalOn-chain transfers 10sOperationalSecurity monitor 47sOperationalBlockmetric Sentiment 3mOperationalOfficial news 6mOperationalAlert delivery 9sOperational10:14:47 UTCData: Blockmetric Research · Binance · Bybit