BananaMaps API · v1

API documentation

The BananaMaps API returns the holder intelligence behind the app — cluster graphs, ranked holders, wallet labels and risk scores — as JSON. Everything is served from the cached snapshot of a token, so responses are fast and identical to what the app shows at that moment.

Base URLhttps://www.bananamaps.io/v1
FormatJSON. Success: { data, meta }. Error: { error: { code, message, status, docs } }.
MethodsGET only (plus OPTIONS for CORS preflight).
CORSOpen (Access-Control-Allow-Origin: *); rate-limit and quota headers are exposed to browsers.
VersioningThe version is in the path. Fields may be added to v1 responses; none will be removed or renamed without a v2.
Chainseth Ethereum · bsc BNB Smart Chain · base Base · sol Solana · robinhood Robinhood Chain

Authentication

Every call carries your API key as a Bearer token. Keys start with bm_live_ and are created in the dashboard; a verified free account gets its first key immediately.

Header
Authorization: Bearer bm_live_YOUR_KEY
Keep keys on the server where you can. We store only a hash of each key and never write keys to logs. If a key leaks, revoke it in the dashboard — requests with it fail immediately with revoked_key — and create a new one. You can hold up to five active keys.

Quickstart

Fetch the top holders of PEPE on Ethereum. The same shape works for any token that has a snapshot.

curl
curl "https://www.bananamaps.io/v1/holders/eth/0x6982508145454ce325ddbe47a25d4ec3d2311933" \
  -H "Authorization: Bearer bm_live_YOUR_KEY"
JavaScript
const res = await fetch(
  "https://www.bananamaps.io/v1/holders/eth/0x6982508145454ce325ddbe47a25d4ec3d2311933",
  { headers: { Authorization: "Bearer " + process.env.BANANAMAPS_KEY } }
);
if (!res.ok) {
  const { error } = await res.json();
  throw new Error(error.code + ": " + error.message);
}
const { data, meta } = await res.json();
console.log(data.token.symbol, data.holders.length, "holders; snapshot", meta.computedAt);

Validation happens before metering: a 400 for a bad chain or address does not count against your quota. Every authorized response, including errors, carries the rate-limit and quota headers.

Graph

The full cluster graph of a token: the mapped holders and the counterparties that connect them (nodes), the transfers observed between them (edges, indexes into nodes) and the connected-wallet clusters. This is the payload the app renders.

GET/v1/graph/{chain}/{address}
ParameterInDescription
chainpathOne of eth, bsc, base, sol, robinhood.
addresspathToken contract address. EVM: 0x + 40 hex characters (case does not matter; responses lowercase it). Solana: base58 mint, 32–44 characters, case-sensitive.

Response fields

FieldTypeDescription
data.tokenobjectToken summary — see token object.
data.nodes[]arrayaddress, kind (holder | counterparty), rank (1-based among holders, null for counterparties), pct of supply, balance, usdValue, label, labelType, cluster id or null, degree (edges touching the node).
data.edges[]arrayfrom and to are indexes into nodes; count is the number of transfers in that direction.
data.clusters[]arrayid, members (node indexes), combined pct of supply.
metaobjectchain, address, computedAt, snapshotAgeSeconds.

Example

curl
curl "https://www.bananamaps.io/v1/graph/eth/0x6982508145454ce325ddbe47a25d4ec3d2311933" \
  -H "Authorization: Bearer bm_live_YOUR_KEY"
200 OK (abridged to three holders)
{
  "data": {
    "token": {
      "chain": "eth",
      "address": "0x6982508145454ce325ddbe47a25d4ec3d2311933",
      "name": "Pepe",
      "symbol": "PEPE",
      "logo": "https://dd.dexscreener.com/ds-data/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933.png",
      "price": 0.0000112,
      "priceChange24h": -3.4,
      "marketCap": 4712000000,
      "volume24h": 812000000,
      "totalHolders": 418223,
      "holdersMapped": 150,
      "computedAt": "2026-10-27T09:14:03.000Z"
    },
    "nodes": [
      {
        "address": "0xf977814e90da44bfa03b6295a0616a897441acec",
        "kind": "holder",
        "rank": 1,
        "pct": 6.84,
        "balance": 28775196000000,
        "usdValue": 322282195,
        "label": "Binance 8",
        "labelType": "cex",
        "cluster": null,
        "degree": 1
      },
      {
        "address": "0x3a4d2a7e6c1f0b9d8e5f4a3b2c1d0e9f8a7b6c5d",
        "kind": "holder",
        "rank": 2,
        "pct": 2.41,
        "balance": 10138629000000,
        "usdValue": 113552645,
        "label": null,
        "labelType": null,
        "cluster": 3,
        "degree": 3
      },
      {
        "address": "0x9e2c7b1a0f3d4e5c6b7a8d9e0f1a2b3c4d5e6f7a",
        "kind": "holder",
        "rank": 3,
        "pct": 1.97,
        "balance": 8287593000000,
        "usdValue": 92821042,
        "label": null,
        "labelType": null,
        "cluster": 3,
        "degree": 2
      },
      {
        "address": "0x7c1e5b9a2d4f6e8c0b3a5d7f9e1c3b5a7d9f1e3c",
        "kind": "counterparty",
        "rank": null,
        "pct": 0,
        "balance": null,
        "usdValue": null,
        "label": null,
        "labelType": null,
        "cluster": 3,
        "degree": 2
      }
    ],
    "edges": [
      {
        "from": 3,
        "to": 1,
        "count": 4
      },
      {
        "from": 3,
        "to": 2,
        "count": 2
      },
      {
        "from": 1,
        "to": 2,
        "count": 1
      },
      {
        "from": 1,
        "to": 0,
        "count": 1
      }
    ],
    "clusters": [
      {
        "id": 3,
        "members": [
          1,
          2,
          3
        ],
        "pct": 4.38
      }
    ]
  },
  "meta": {
    "chain": "eth",
    "address": "0x6982508145454ce325ddbe47a25d4ec3d2311933",
    "computedAt": "2026-10-27T09:14:03.000Z",
    "snapshotAgeSeconds": 412
  }
}

Holders

The top 150 holders of a token, ranked by balance, with their share of supply, USD value, label and cluster.

GET/v1/holders/{chain}/{address}
ParameterInDescription
chainpathOne of eth, bsc, base, sol, robinhood.
addresspathToken contract address. EVM: 0x + 40 hex characters (case does not matter; responses lowercase it). Solana: base58 mint, 32–44 characters, case-sensitive.

Response fields

FieldTypeDescription
data.tokenobjectToken summary — see token object.
data.holders[]arrayUp to 150 entries: rank, address, pct (percent of total supply), balance (token units), usdValue, label, labelType (cex | dex | burn | contract | null), cluster id or null.
metaobjectchain, address, computedAt, snapshotAgeSeconds.

Example

curl
curl "https://www.bananamaps.io/v1/holders/eth/0x6982508145454ce325ddbe47a25d4ec3d2311933" \
  -H "Authorization: Bearer bm_live_YOUR_KEY"
200 OK (abridged to three holders)
{
  "data": {
    "token": {
      "chain": "eth",
      "address": "0x6982508145454ce325ddbe47a25d4ec3d2311933",
      "name": "Pepe",
      "symbol": "PEPE",
      "logo": "https://dd.dexscreener.com/ds-data/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933.png",
      "price": 0.0000112,
      "priceChange24h": -3.4,
      "marketCap": 4712000000,
      "volume24h": 812000000,
      "totalHolders": 418223,
      "holdersMapped": 150,
      "computedAt": "2026-10-27T09:14:03.000Z"
    },
    "holders": [
      {
        "rank": 1,
        "address": "0xf977814e90da44bfa03b6295a0616a897441acec",
        "pct": 6.84,
        "balance": 28775196000000,
        "usdValue": 322282195,
        "label": "Binance 8",
        "labelType": "cex",
        "cluster": null
      },
      {
        "rank": 2,
        "address": "0x3a4d2a7e6c1f0b9d8e5f4a3b2c1d0e9f8a7b6c5d",
        "pct": 2.41,
        "balance": 10138629000000,
        "usdValue": 113552645,
        "label": null,
        "labelType": null,
        "cluster": 3
      },
      {
        "rank": 3,
        "address": "0x9e2c7b1a0f3d4e5c6b7a8d9e0f1a2b3c4d5e6f7a",
        "pct": 1.97,
        "balance": 8287593000000,
        "usdValue": 92821042,
        "label": null,
        "labelType": null,
        "cluster": 3
      }
    ]
  },
  "meta": {
    "chain": "eth",
    "address": "0x6982508145454ce325ddbe47a25d4ec3d2311933",
    "computedAt": "2026-10-27T09:14:03.000Z",
    "snapshotAgeSeconds": 412
  }
}

The token object

Graph, holders and risk responses share data.token:

FieldDescription
chain, addressAs requested; EVM addresses lowercased.
name, symbol, logoToken metadata; logo may be null.
price, priceChange24hUSD price and 24 h change in percent at snapshot time; null when unknown.
marketCap, volume24hUSD; null when unknown.
totalHoldersOn-chain holder count where the chain exposes it (EVM); null otherwise.
holdersMappedHow many holders are in the snapshot (up to 150).
computedAtISO timestamp of the snapshot.

Labels

The label for one wallet address: centralized exchange, DEX or AMM contract, burn address or other contract. Labels come from our curated list first, then from labels resolved while building maps, then from address patterns (burn addresses).

GET/v1/labels/{chain}/{wallet}
ParameterInDescription
chainpathOne of eth, bsc, base, sol, robinhood.
walletpathThe wallet address to label (same address rules as tokens). Does not need a snapshot.

Response fields

FieldTypeDescription
data.addressstringThe wallet, normalized.
data.labelstring | nullHuman label such as Binance 8 or Burn Address.
data.labelTypeenum | nullcex, dex, burn, contract, or null when unlabeled.
data.sourceenum | nullcurated (our list), resolved (learned while mapping), pattern (address shape), or null.
metaobjectchain, address.

Example

curl
curl "https://www.bananamaps.io/v1/labels/eth/0xf977814e90da44bfa03b6295a0616a897441acec" \
  -H "Authorization: Bearer bm_live_YOUR_KEY"
200 OK
{
  "data": {
    "address": "0xf977814e90da44bfa03b6295a0616a897441acec",
    "label": "Binance 8",
    "labelType": "cex",
    "source": "curated"
  },
  "meta": {
    "chain": "eth",
    "address": "0xf977814e90da44bfa03b6295a0616a897441acec"
  }
}

Risk

A 0–100 score of how concentrated and clustered a token's supply is, with the factors behind it and a summary of each cluster. Higher is healthier. Exchange, DEX and burn wallets are excluded from the concentration factors; labeled contracts (vesting, treasuries) count as holders, as in the app. clusterCount counts every cluster; meaningfulClusterCount is the number the Cluster Activity factor scores (clusters with at least two top holders).

GET/v1/risk/{chain}/{address}
ParameterInDescription
chainpathOne of eth, bsc, base, sol, robinhood.
addresspathToken contract address. EVM: 0x + 40 hex characters (case does not matter; responses lowercase it). Solana: base58 mint, 32–44 characters, case-sensitive.

Response fields

FieldTypeDescription
data.tokenobjectToken summary — see token object.
data.risk.scorenumber0–100. ≥ 80 Low Risk · 50–79 Medium · 25–49 High · < 25 Very High.
data.risk.labelstringThe band name for score.
data.risk.factors[]arrayname, score (0–100), weight (percent of the total), detail (human explanation). Weights sum to 100.
data.risk.top10PctnumberShare of supply held by the ten largest mapped holders, exchanges included.
data.risk.clusterCountnumberNumber of clusters in the snapshot.
data.risk.clusters[]arrayid, wallets (all members), holders (members in the top 150), combined pct, members (addresses).
metaobjectchain, address, computedAt, snapshotAgeSeconds.
The score describes supply structure at snapshot time. It is informational, not financial advice, and says nothing about a token's contract code, liquidity or team.

Example

curl
curl "https://www.bananamaps.io/v1/risk/eth/0x6982508145454ce325ddbe47a25d4ec3d2311933" \
  -H "Authorization: Bearer bm_live_YOUR_KEY"
200 OK
{
  "data": {
    "token": {
      "chain": "eth",
      "address": "0x6982508145454ce325ddbe47a25d4ec3d2311933",
      "name": "Pepe",
      "symbol": "PEPE",
      "logo": "https://dd.dexscreener.com/ds-data/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933.png",
      "price": 0.0000112,
      "priceChange24h": -3.4,
      "marketCap": 4712000000,
      "volume24h": 812000000,
      "totalHolders": 418223,
      "holdersMapped": 150,
      "computedAt": "2026-10-27T09:14:03.000Z"
    },
    "risk": {
      "score": 64,
      "label": "Medium Risk",
      "factors": [
        {
          "name": "Top Holder",
          "score": 85,
          "weight": 25,
          "detail": "2.4% held by #1 non-exchange wallet"
        },
        {
          "name": "Top 10 Concentration",
          "score": 75,
          "weight": 25,
          "detail": "18.6% held by top 10 (excl. 4 exchanges)"
        },
        {
          "name": "Cluster Activity",
          "score": 30,
          "weight": 25,
          "detail": "4 clusters detected"
        },
        {
          "name": "Holder Count",
          "score": 75,
          "weight": 15,
          "detail": "146 holders mapped"
        },
        {
          "name": "Distribution",
          "score": 50,
          "weight": 10,
          "detail": "Top 5 hold 13.9%"
        }
      ],
      "top10Pct": 25.42,
      "clusterCount": 4,
      "clusters": [
        {
          "id": 3,
          "wallets": 3,
          "holders": 2,
          "pct": 4.38,
          "members": [
            "0x3a4d2a7e6c1f0b9d8e5f4a3b2c1d0e9f8a7b6c5d",
            "0x9e2c7b1a0f3d4e5c6b7a8d9e0f1a2b3c4d5e6f7a",
            "0x7c1e5b9a2d4f6e8c0b3a5d7f9e1c3b5a7d9f1e3c"
          ]
        }
      ]
    }
  },
  "meta": {
    "chain": "eth",
    "address": "0x6982508145454ce325ddbe47a25d4ec3d2311933",
    "computedAt": "2026-10-27T09:14:03.000Z",
    "snapshotAgeSeconds": 412
  }
}

Errors

Errors use the HTTP status and a stable code. The message is written for humans and may change; branch on code.

CodeStatusMeaning
bad_request400Unknown chain, or the address is not a valid address for that chain. Not metered.
unauthorized401No Authorization header, or it is not "Bearer <key>".
invalid_key401The key is malformed or does not exist.
revoked_key401The key was revoked in the dashboard. Create a new one.
account_canceled403The account is closed.
not_found404Unknown route.
not_indexed404No snapshot exists for this token yet. The message carries the app link that builds one.
quota_exceeded429The monthly quota of your plan is used up. Retry-After points at the reset; upgrade for more.
rate_limited429Too many calls this minute. Retry-After says how many seconds to wait.
server_error500Something failed on our side. Safe to retry.

Example

429 Too Many Requests
{
  "error": {
    "code": "quota_exceeded",
    "message": "Monthly quota of 1,000 calls reached on the Free plan. Upgrade at https://www.bananamaps.io/developers/dashboard for a higher quota, or wait for the reset on Sun, 01 Nov 2026 00:00:00 GMT.",
    "status": 429,
    "docs": "https://www.bananamaps.io/developers/docs"
  }
}
404 Not Found (no snapshot yet)
{
  "error": {
    "code": "not_indexed",
    "message": "No snapshot for this token yet. Open https://www.bananamaps.io/app?token=0x6982508145454ce325ddbe47a25d4ec3d2311933&chain=eth once to build it, then call again.",
    "status": 404,
    "docs": "https://www.bananamaps.io/developers/docs"
  }
}

Rate limits & quotas

Each plan has a per-minute rate limit and a monthly quota. Both are hard limits: at the quota you receive 429 quota_exceeded until the period resets or you upgrade — there is no overage billing. Every authorized call counts as one, whichever endpoint it hits, including a 404 not_indexed answer. Requests rejected before authentication (400 for a bad chain or address, 401) are not counted.

PlanCalls / monthCalls / minutePrice
Free1,00030Free
Starter50,000120$49/mo · $490/yr
Pro500,000600$199/mo · $1,990/yr

Headers

Sent on every authorized response, success or error:

HeaderMeaning
X-RateLimit-LimitCalls allowed per minute on your plan.
X-RateLimit-RemainingCalls left in the current minute.
X-RateLimit-ResetUnix time (seconds) when the minute window resets.
X-Quota-LimitCalls allowed this month on your plan.
X-Quota-UsedCalls counted this month, including this one.
X-Quota-ResetISO timestamp of the start of the next monthly period.
Retry-AfterOnly on 429: seconds until you may call again.

Monthly periods are calendar months in UTC. Usage per key and the reset date are on your dashboard.

Snapshots

BananaMaps builds a holder map by pulling a token's top 150 holders and replaying the transfers between them. That build takes a minute or two, so results are stored as a snapshot and refreshed in the background. The API serves snapshots; it never starts a build.

  • meta.computedAt is when the snapshot was computed and meta.snapshotAgeSeconds how old it is at response time. Popular tokens refresh often; quiet ones less so.
  • If no snapshot exists you get 404 not_indexed. Open the token in the app once (the link is in the message) — the build runs, and the API answers from then on.
  • The labels endpoint does not need a snapshot; it answers for any address.
  • Responses are not cached by the API (Cache-Control: no-store), so you always see the newest snapshot; cache on your side as you see fit.
BananaMaps Embed

Embed quickstart

BananaMaps Embed puts the live holder map on your own pages. Register your site in the dashboard to get a partner id, then paste this snippet where the map should appear:

HTML
<iframe
  src="https://www.bananamaps.io/embed?token=TOKEN_ADDRESS&chain=eth&pid=YOUR_PID"
  width="100%"
  height="600"
  style="border:0;border-radius:12px"
  loading="lazy"
  title="BananaMaps Embed"
></iframe>
First-party domains (bananamaps.io, bananascreener.io and their subdomains) need no pid. Any other domain must pass a valid pid whose domain list matches the page, or visitors see a “get a free Embed key” message in place of the map. The frame is responsive: set width and height to fit your layout; 600 px tall works well.

Embed parameters

ParameterRequiredDescription
tokenrequiredToken contract address (EVM 0x…, or a Solana mint).
chainrequiredOne of eth, bsc, base, sol, robinhood.
pidrequired*Your partner id from the dashboard (bme_…). *Not needed on first-party domains.
badgePartner planbadge=0 hides the "Powered by BananaMaps" badge. Ignored on the Free plan.
accentPartner planSix-digit hex without #, e.g. accent=00C805. Replaces the gold accent in the map. Ignored on the Free plan.
  • Free plan: the “Powered by BananaMaps” badge and link are required and always shown; badge and accent are ignored.
  • Partner plan: badge=0 and accent take effect. You can also save a default badge setting and accent per site in the dashboard, so the snippet stays short.
  • Domains: the id only works on the hostnames you listed (wildcards like *.example.com allowed). Leave the list empty while testing and lock it down before launch.
  • Refresh: maps come from the same snapshots as the API. Partner sites get priority refresh for the tokens their visitors open.
  • Metering: maps shown through BananaMaps Embed never count against your API quota.

Plans and pricing: BananaMaps Embed. Manage sites, badge and accent in the dashboard.