---
title: "Live holders, holder count + concentration"
method: GET
path: "/tokens/{mint}/holders"
tags: ["Tokens"]
---

# Live holders, holder count + concentration

`GET /tokens/{mint}/holders`

## Request

- Base URL: `https://madeonsol.com/api/v1`
- URL: `https://madeonsol.com/api/v1/tokens/{mint}/holders`
- Auth: HTTP bearer

A live holder census read from the ledger at `confirmed`: every token account of the mint (owner + balance via mint-scoped `getProgramAccounts`), merged per owner. Gives an EXACT `holder_count` (distinct non-zero owners minus excluded pools/curves/burns), `token_accounts_nonzero`, ranks 1–100 retained, and `top1/top10/top20/top50/top100_share` over the circulating denominator. Each disclosed owner is labelled from MadeOnSol wallet intelligence (deployer / kol / early_buyer / bundle / bot / dump_cluster). Program-derived owners (liquidity pools, bonding curves, vaults) and burn addresses are EXCLUDED from the denominator and listed in `excluded[]` — the #1 raw account of a fresh memecoin is its own bonding curve. Disclosure: PRO ranks 1–10, ULTRA 1–50, BUSINESS 1–100; the maths is tier-independent. Measured latency: fresh pump.fun mints <1 s, $0.1–2M memecoins ~0.1–1.2 s, 200k–550k-account tokens 6–11 s (503 `holder_scan_in_progress` then cached). Mega-caps the provider refuses (TRUMP/JUP/BONK class) fall back to the top-20 `getTokenLargestAccounts` view with `holder_count: null` and `source.census_fallback_reason` set — never a trade-derived estimate. The same fallback applies when the census times out, and (since 2026-10-02) when a census's balances add up to more than the mint's supply: such a read is not one consistent ledger snapshot, so it is refused rather than served (`census_fallback_reason: census_inconsistent_sum_exceeds_supply`). Distinct from `/cap-table` (who bought first) — this is who holds NOW.

## Path parameters

- `mint` string, required

## Query parameters

- `include_wallet_flags` boolean

## Response `200`

OK

- object
  - `mint` string
  - `slot` integer: Ledger slot the holder set was read at.
  - `as_of` string, date-time
  - `holders` object[]
    - `rank` integer
    - `owner` string: Wallet (owner of the token account(s)).
    - `token_accounts` string[]
    - `amount_raw` string: Raw u64 balance as a string — never a float.
    - `amount` number, nullable
    - `pct_of_supply` number, nullable
    - `pct_of_circulating` number, nullable: Share of supply minus pools/curves/burns.
    - `labels` string[]: Empty = unknown to us, not verified clean. sniper / bundler_wallet / dumper / alpha_tracked appear only with include_wallet_flags=true.
    - `kol_name` string, nullable
    - `early_buyer_rank` integer, nullable
    - `bot_confidence` 'none' | 'low' | 'medium' | 'high', nullable
    - `historical_win_rate` number, nullable
    - `wallet_flags` object, nullable: Only with include_wallet_flags=true; null when the flag sources were unavailable.
      - `is_sniper` boolean
      - `is_bundler` boolean
      - `is_dumper` boolean
      - `is_kol` boolean
      - `is_alpha_tracked` boolean
      - `is_bot_high_confidence` boolean
      - `label_coverage` object: Per label: evaluated=false means no sufficient evidence (not a verified negative).
  - `count` integer
  - `disclosed` integer: Rank cap by tier: 10 PRO, 50 ULTRA, 100 BUSINESS.
  - `wallet_flags_meta` object: Only with include_wallet_flags=true.
    - `status` 'available' | 'partial' | 'unavailable'
    - `rule_version` string
    - `note` string
  - `excluded` object[]
    - `owner` string
    - `token_accounts` string[]
    - `amount_raw` string
    - `pct_of_supply` number, nullable
    - `reason` 'pool' | 'bonding_curve' | 'burn' | 'program_account': pool = vault authority of a known pool (dex + pool_address set); bonding_curve = pump.fun / LaunchLab curve; burn = incinerator / system program; program_account = off-curve owner we could not attribute to a known pool (vault, escrow, staking, unknown pool).
    - `dex` string, nullable: pumpfun | pumpswap | raydium | launchlab | meteora | orca | jupiter — when named.
    - `pool_address` string, nullable
  - `concentration` object
    - `holder_count` integer, nullable: Exact distinct non-zero owners minus excluded pools/curves/burns, at slot (census). null only when the census was not served (provider refusal, timeout or an inconsistent census; see source.census_fallback_reason) — never estimated from trades.
    - `holder_count_source` string, nullable
    - `token_accounts_nonzero` integer, nullable
    - `supply_raw` string, nullable: Percentage denominator. Normally the on-chain mint supply read in this request; null when no supply consistent with the disclosed balances is available (every percentage is then null, never above 100).
    - `supply_source` 'mint_account' | 'token_prices_cache' | 'rpc_token_supply', nullable
    - `supply_consistent` boolean: false = the only available supply was below what the disclosed owners hold, so percentages are withheld.
    - `circulating_raw` string, nullable
    - `decimals` integer, nullable
    - `top1_share` number, nullable
    - `top10_share` number, nullable
    - `top20_share` number, nullable
    - `top50_share` number, nullable
    - `top100_share` number, nullable
    - `pool_and_program_pct` number, nullable: Share of TOTAL supply sitting in pools/curves/vaults/burns among the excluded owners (= pool_pct + burned_pct + program_pct).
    - `pool_pct` number, nullable: Share of total supply in NAMED pools + bonding curves (excluded[].reason pool | bonding_curve).
    - `burned_pct` number, nullable: Share of total supply at burn addresses.
    - `program_pct` number, nullable: Share of total supply held by off-curve owners we could not attribute (excluded[].reason program_account).
    - `deployer_pct` number, nullable
    - `kol_pct` number, nullable
    - `early_buyer_pct` number, nullable
    - `bundle_pct` number, nullable
    - `bot_pct` number, nullable
    - `dump_cluster_pct` number, nullable
    - `distinct_owners_in_top20` integer
    - `ranked_owners_available` integer: How many ranked owners the scan retained (≤100 census, ≤20 fallback).
  - `deployer` object, nullable
    - `wallet` string
    - `tier` string
    - `bonding_rate` number, nullable
  - `source` object
    - `method` 'getProgramAccounts_census' | 'getTokenLargestAccounts'
    - `token_program` string, nullable
    - `rpc_cap` integer
    - `commitment` string
    - `scan_ms` integer
    - `census_fallback_reason` string, nullable: Why the full census was not served (null on a census). `census_exceeds_provider_limit` = the provider refused it (mega-caps); `census_timed_out`; `census_inconsistent_sum_exceeds_supply` = the census balances summed past the mint supply (or the supply could not be read), so it was refused (2026-10-02); `census_failed: <detail>` otherwise. `note` explains it in prose.
    - `note` string

## Other responses

- `401`: Missing or invalid API key, or an expired free-tier key. Authentication-stage error: no `X-RateLimit-*` headers, no `_rid`.
- `403`: Forbidden. One of: the key's tier is below this endpoint's or parameter's gate (`tier_required` with an `upgrade` block); the public demo key used outside its allow-list (`demo_key_endpoint_not_allowed`); an invalid or revoked key; an origin-restricted key called from a disallowed origin (`code` `origin_not_allowed` / `origin_required`). `X-RateLimit-*` headers are present only on `tier_required`.
- `404`: Mint not found on-chain (`error_kind: not_a_mint`).
- `429`: Too many requests. Every 429 carries `Retry-After`. A quota 429 (`rate_limit_exceeded`, with `X-RateLimit-*`), a heavy-query concurrency 429 (`error_kind` `concurrency`/`capacity`), or the per-IP authentication-failure block (30 failed auths/min; plain `error` + `retry_after_seconds`, no `X-RateLimit-*`).
- `500`: Unexpected server error from the route (`Internal server error`, with `_rid`). Safe to retry with backoff. A failed API-key lookup is a 503 `auth_backend_unavailable`, never a 500.
- `503`: `error_kind: holder_scan_in_progress` (Retry-After 20) — large established tokens take 5–30 s to enumerate upstream; the scan continues and is cached, so the retry is instant. Or `holder_rpc_unavailable` (Retry-After 15). Either way we fail closed rather than serve a trade-derived guess.

---

[API](https://skmtc.dev/madeonsol/apis/madeonsol-api.md) · [All operations](https://skmtc.dev/madeonsol/apis/madeonsol-api/llms.txt) · [OpenAPI document](https://skmtc.dev/madeonsol/apis/madeonsol-api/revisions/96ce84657f55?raw)
