---
title: "List a market's graded holders"
method: GET
path: "/api/v1/market/{condition_id}/holders"
tags: ["Markets"]
---

# List a market's graded holders

`GET /api/v1/market/{condition_id}/holders`

## Request

- Base URL: `https://api.0xinsider.com`
- URL: `https://api.0xinsider.com/api/v1/market/{condition_id}/holders`
- Auth: one of:
  - HTTP bearer
  - OAuth 2
    - Authorization code: `https://0xinsider.com/oauth/authorize`
    - Token URL: `https://api.0xinsider.com/oauth/token` scopes: `read`
- TypeScript: <https://skmtc.dev/0xinsider/apis/0xinsider-api/docs/api/v1/market/:condition_id/holders/get.ts> (a function with typed inputs; its response is checked against the schema)

The graded holder roster of one market, the list a Pick of the Day shows for its market, for any Polymarket market: every S/A/B wallet with open shares on either outcome, from a complete provider holder scan, each with its shares, Polymarket's own currentValue for the leg, its grade, its win record in the market's category, and its badges. Both outcomes are listed; a wallet holding both is listed once on its net side. Ordered by current_value_usd DESC, then shares DESC, then address. The roster is computed at most once a minute per market and shared by every caller; a page is cut from that roster, so paging is cheap and `total` is exact. The route never widens past the graded cohort: `scan.wallet_count` reports every wallet the walk saw, graded or not. An incomplete, unstable or failed scan is a 503 with Retry-After, never a shorter list. A complete scan in which no S, A or B wallet holds either outcome is a 200 with an empty holders list and zero totals; retrying cannot change it. The cohort is S, A and B by the wallet's current grade (`traders.latest_grade`); a C, D, F or ungraded wallet is never listed here and appears only inside `scan.wallet_count`. A count here will not match GET /api/v1/positions for the same market, and the cohort is only one of three reasons: /positions reads the `wallet_positions` mirror rather than this live provider scan, and without a `wallet` its `min_size` defaults to 100 USD, so it drops the smaller legs this route keeps. Read a difference as all three, never as a grade count. This is current state and takes no as-of parameter: the roster is at most 60 s old over a provider walk at most 180 s old, and `scan.fetched_at` is the only timestamp on it. For a point-in-time roster, snapshot this route yourself on your own schedule; the one frozen roster the API serves is `display_holders` on GET /api/v1/pick-of-the-day, for that pick's market and backed side at publication.

## Path parameters

- `condition_id` string, required

## Query parameters

- `outcome` 'yes' | 'no' | 'all'
- `min_grade` 'S' | 'A' | 'B'
- `limit` integer
- `cursor` string

## Headers

- `X-Query-Validation` 'strict'
- `If-None-Match` string

## Response `200`

One page of the market's graded holder roster.

- object
  - `object` 'list', required
  - `data` MarketHolder[], required
    - `id` string, required: Composite `pos_<wallet>:<condition_id>:<outcome_index>`, the same id `GET /api/v1/positions` gives this leg.
    - `trader_id` string, required: `trd_`-prefixed trader id, accepted by `GET /api/v1/trader/{address}`.
    - `address` string, required: Lowercased proxy wallet.
    - `name` string, nullable, required: Polymarket's public display name from the same holder snapshot; null for a private wallet, which the provider redacts at the source.
    - `side` 'YES' | 'NO', required: The outcome this wallet nets to. A wallet holding both outcomes is listed once, on its net side; see `other_outcome_shares`.
    - `token_id` string, nullable, required: Polymarket CLOB token id (ERC1155 asset id, decimal string) for `side`; null when the market has no stored token id.
    - `grade` 'S' | 'A' | 'B', nullable, required: All-time trader grade. This route lists the S/A/B cohort only, the same cohort a Pick of the Day roster lists.
    - `shares` number, required: Shares held on `side`, from the complete provider scan.
    - `current_value_usd` number, nullable, required: Polymarket's own currentValue for this leg, in USD. Null when the snapshot predates the value map.
    - `other_outcome_shares` number: Shares on the other outcome when the wallet holds both. Absent when the wallet is one-sided.
    - `last_traded_at` string, date-time, nullable, required: The wallet's last recorded trade anywhere, null when unknown.
    - `category_win_rate` number: This wallet's win rate in the market's canonical category (`market.category`): the share of its resolved markets in that category whose realized P&L closed positive, as a 0..1 fraction. Present only with `category_win_rate_status` = `measured`. The same read `GET /api/v1/trader/{address}/categories` serves per wallet.
    - `category_win_record` object: The two counts `category_win_rate` is the ratio of, from the same row: `wins / decided` equals the rate. Every resolved Polymarket market the wallet traded in the market's canonical category, at any position size, rebuilt daily. Present only with `category_win_rate_status` = `measured`.
      - `wins` integer, required: Resolved markets in the category that this wallet closed with a profit.
      - `decided` integer, required: Resolved markets in the category that this wallet closed with a profit or a loss. A market resolved at zero realized P&L is in neither count.
    - `category_win_rate_game` string: For an esports market, the game `category_win_rate` and `category_win_record` were measured in: `LoL`, `CS2`, `Dota 2`, `Valorant`, `Call of Duty`, `Honor of Kings`, `Mobile Legends: Bang Bang`, `Overwatch`, `Rainbow Six Siege`, `Rocket League` or `StarCraft II`. Present only when the wallet's record in that game clears the 5-resolved-market floor, in which case the rate and record are the game's rather than the `Esports` bucket's. Absent when the rate is the bucket's (the wallet's game history is under the floor), on every non-esports market, and beside every non-measured status. Label the rate with this when present and with the market's `category` otherwise.
    - `category_win_rate_status` 'measured' | 'not_enough_data' | 'unavailable', required: Why `category_win_rate` is present or absent: `measured` (rate present), `not_enough_data` (under the floor of 5 decided markets in the category), or `unavailable` (the market has no canonical category, or the read failed; retry later). Always present on this route.
    - `category_evidence` HolderCategoryEvidence: Current category evidence, independent of the global grade. Pick of the Day stamps only the served display roster; frozen entry snapshots remain unchanged.
      - `status` 'live' | 'insufficient' | 'stale' | 'unknown' | 'degraded', required
      - `canonical_category` string
      - `skill` CategorySkillV2: Forward-only category evidence from observed Polymarket taker fills. Status is category eligibility, not a global letter grade or a guarantee of positive edge. Scores are probability differences. Unknown and degraded rows withhold scores. Coverage is partial; observation counts are not lifetime market counts.
        - `status` 'live' | 'insufficient' | 'stale' | 'unknown' | 'degraded', required
        - `model_version` string, required
        - `taxonomy_version` string, nullable, required
        - `platform` 'polymarket', required
        - `scope` 'observed_goldsky_primary_taker_fill', required
        - `source_coverage` 'partial_whale_threshold_fills' | 'graded_wallet_fills', required
        - `canonical_category` string, required
        - `as_of` string, date-time, required
        - `source_last_success_at` string, date-time, nullable, required
        - `observation_started_at` string, date-time, required
        - `latest_observation_at` string, date-time, nullable, required
        - `independent_event_count` integer, required
        - `resolved_condition_count` integer, required
        - `unresolved_observation_count` integer, required
        - `edge_mean` number, nullable, required
        - `edge_sd` number, nullable, required
        - `edge_se` number, nullable, required
        - `edge_lower_95` number, nullable, required
        - `brier_event_avg` number, nullable, required
    - `wallet_age_days` number, nullable: Days since this wallet's first trade. The five badge fields are present together, only for a wallet that draws at least one badge.
    - `is_new_wallet` boolean: True when the wallet's first trade was under 30 days ago.
    - `markets_traded` integer, nullable: Distinct markets the wallet has traded.
    - `is_bot` boolean: True when the wallet is flagged as automated.
    - `x_username` string, nullable: The wallet's X handle without the @, when linked.
  - `has_more` boolean, required
  - `next_cursor` string: Absent on the last page.
  - `total` integer: Holders matching the request's `outcome` and `min_grade` filters across every page.
  - `market` MarketHoldersMarket, required
    - `id` string, required: `mkt_`-prefixed market id.
    - `condition_id` string, required
    - `title` string, nullable, required
    - `slug` string
    - `event_slug` string
    - `category` string, nullable, required: The canonical category bucket `category_win_rate` is measured in.
    - `outcome_yes` string, nullable, required
    - `outcome_no` string, nullable, required
    - `token_id_yes` string, nullable, required
    - `token_id_no` string, nullable, required
    - `end_date` string, date-time
    - `status` 'open' | 'closed' | 'resolved', required: A settled market's roster is whatever the provider still lists as open; it empties as holders redeem.
  - `scan` MarketHoldersScan, required
    - `source` 'cached' | 'live', required: `cached`: the shared holder snapshot, at most 180 s old at compute time. `live`: the provider answered a fresh complete walk for this compute.
    - `complete` boolean, required: Always true on a served roster. An incomplete or unstable scan is a 503, never a partial list.
    - `fetched_at` string, date-time, nullable, required: When the provider walk that produced the holder population finished.
    - `wallet_count` integer, required: Every distinct wallet the complete walk saw with open shares, graded or not. The roster lists the graded S/A/B subset.
  - `totals` MarketHoldersTotals, required: Roster totals BEFORE any `outcome` or `min_grade` filter, so a page always knows the whole market it was cut from.
    - `graded_count` integer, required: Distinct graded wallets with net exposure; a two-sided wallet counts once.
    - `yes_count` integer, required
    - `no_count` integer, required
    - `yes_shares` number, required
    - `no_shares` number, required
    - `yes_usd` number, required: Polymarket currentValue summed over the graded wallets netting YES.
    - `no_usd` number, required: Polymarket currentValue summed over the graded wallets netting NO.
    - `yes_grades` MarketHoldersSideGrades, required
      - `s` integer, required
      - `a` integer, required
      - `b` integer, required
    - `no_grades` MarketHoldersSideGrades, required
      - `s` integer, required
      - `a` integer, required
      - `b` integer, required
  - `meta` ResponseMeta, required
    - `request_id` string, required: Unique request ID (req_ prefix). The same value as the X-Request-Id response header, the request's usage accounting row and its log lines.
    - `cached` boolean, required
    - `cache_age_s` integer: Cache age in seconds. Omitted when the response was not cached, and also when it was cached but its age cannot be established (an entry stored before its cache carried a computed instant). Never a placeholder: an unknown age is reported as no value rather than as the cache TTL.
    - `cost` integer, required: Advisory request weight (relative compute cost). 1 for simple reads; higher for heavier endpoints. Not a credit/price.
    - `ranking_generation` integer: Committed PostgreSQL-owned leaderboard generation for the returned rows and cursor. Present on GET /api/v1/leaderboard; omitted on endpoints that do not read this ranking.
    - `ranking_as_of` string, date-time: Authoritative RFC3339 timestamp from cache_generations.updated_at for ranking_generation. It is read in the same repeatable-read snapshot as the leaderboard rows and is not request time, cache write time, or row insertion order.
    - `directional_source` 'live' | 'degraded': Which path produced the team-directional read on this response. Only present on endpoints that compute one (today: GET /api/v1/sports-edge-signals). "live" means the read RAN. "degraded" means it FAILED, so nothing was measured and the ranking fell back to raw conviction. The flag describes the READ, not its consequence: a read that ran and found nothing groupable also leaves the directional fields null, and that is honestly "live" -- the per-signal nulls already say "nothing to enrich here", so this snapshot-level flag carries only what they cannot, namely whether the read ran at all. A degraded response is cached on the shorter degraded TTL so it self-heals. Reported SEPARATELY from ranking_source because the two degradations are independent -- a sharp-money DB miss weakens the ranking DATA, a directional failure removes a ranking WEIGHT -- and a consumer down-weighting a degraded response needs to know which input it lost. Omitted on endpoints that compute no directional read.
    - `ranking_source` 'live' | 'db_only': Which ranking-data path produced this response. Only present on endpoints that can degrade a ranking (today: GET /api/v1/sports-edge-signals). "live" is the normal path (the current holder pile from the provider batch); "db_only" is the degraded fallback (a truthful but weaker trader_markets ranking) served when the live sharp-money ranking batch is unavailable (a sharp-money DB read failure, not a Polymarket outage) and cached on a shorter TTL, so a consumer can down-weight or skip it. Omitted on endpoints that never degrade.
    - `category_skill_source` 'live' | 'partial' | 'degraded' | 'unavailable': Whole filtered snapshot category-evidence status before pagination. Operational live always remains partial source coverage.
    - `category_skill_model_version` string
    - `category_skill_taxonomy_version` string
    - `category_skill_platform` 'polymarket'
    - `category_skill_scope` 'observed_goldsky_primary_taker_fill'
    - `category_skill_source_coverage` 'partial_whale_threshold_fills' | 'graded_wallet_fills'
    - `category_skill_observation_started_at` string, date-time
    - `category_skill_model_operationally_degraded` boolean: Whole-model operational readiness captured with the category model snapshot. Present on category-enriched responses even when the filtered signal list is empty. When true, category_skill_source is degraded and sports-edge-signals uses the shorter degraded cache TTL.
    - `category_skill_status_counts` object
      - `live` integer, required
      - `insufficient` integer, required
      - `stale` integer, required
      - `unknown` integer, required
      - `degraded` integer, required
    - `category_skill_base_payload_hash` string: SHA-256 of the funded signal membership/order/rank/cursor vector immediately before category-skill enrichment. Sports-edge-signals only.
    - `category_skill_enriched_base_payload_hash` string: Independent SHA-256 recomputation over the same base fields immediately after category-skill enrichment. Equality with category_skill_base_payload_hash proves shadow enrichment did not change funded inputs. Sports-edge-signals only.

## Other responses

- `304`: Not Modified. Returned when If-None-Match matches the current payload.
- `400`: Invalid request parameter
- `401`: Missing or invalid API key
- `402`: Active Pro subscription required. The key is valid but the account has no active Pro subscription; error.reason is subscription_inactive and error.message names the reactivation URL (https://0xinsider.com/billing). Permanent until a person reactivates: no Retry-After, never retry on a schedule.
- `403`: Account access denied
- `404`: No Polymarket market has this condition_id.
- `408`: The handler did not answer inside the server's 30-second timeout. error.code is request_timeout. On GET and HEAD the response carries Retry-After and error.retry_at; on a mutation it carries neither, because the request may have completed on the server: check its state before repeating it, and reuse its Idempotency-Key.
- `429`: Rate limit exceeded. Three independent budgets. (1) 100 requests/minute per user (sliding window), on every authenticated route. (2) On the BATCH routes only: 2500 batch item units/minute per user, reserved before any item is executed. A batch with N requested items costs N item units, including duplicate and invalid items. 2500 = 100 requests x 25 items per batch, which is the most item work a key can buy through the request limiter at all: a caller may spend their entire 100-request minute on full 25-item batches without the item budget being what stops them. The REQUEST budget is the effective ceiling, and batching is never the more expensive choice. The item budget can still deny at a sliding-window boundary (both counters carry the previous window forward with a floor, and the item counter runs 25x the request counter), so honor a 429 from either. Over-quota batches return 429 with Retry-After plus RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset before any item work is done. (3) The monthly quota: Pro includes 250,000 authenticated requests per UTC calendar month, whatever the billing cadence. Over 250,000: with pay as you go on, requests keep answering and the excess bills at USD 0.20 per 1,000 on a monthly invoice, up to 1,000,000 requests a month; without it, from October 1, 2026, the next request answers 429 rate_limited with error.reason monthly_quota_exceeded and a Retry-After to the month's reset, and from the same day a pay-as-you-go account answers the same past 1,000,000. A refused request is not counted. Every authenticated response carries X-Monthly-Quota-Limit, X-Monthly-Quota-Remaining, and X-Monthly-Quota-Reset (unix seconds, the first of next month). (4) The per-address budget: 1200 requests/minute per IP, shared by every caller behind one address and counted before authentication, on every route. A 429 from it carries error.reason ip_rate_limited and describes that bucket in RateLimit-*; a throttled address (sustained over-limit traffic) carries error.reason ip_throttled with a Retry-After of minutes to days, and a request before it does not shorten the cooldown. Every 429 is the standard error envelope with meta.request_id equal to X-Request-Id.
- `503`: The holder scan could not be served: the provider walk was incomplete, unstable, unavailable, or ran past its 20 s budget, or the roster's single-flight lease expired with nothing published. The body's error.reason is read_model_warming; retry after Retry-After. A shorter list is never served in place of the market.

## Changes

- **2026-09-23** `ece7a25b7a44`: 8 warning, 8 info
  - added the new `freshness_ceiling_unsatisfied` enum value to the `error/reason` response property for the response status `400`
  - added the new `freshness_ceiling_unsatisfied` enum value to the `error/reason` response property for the response status `401`
  - added the new `freshness_ceiling_unsatisfied` enum value to the `error/reason` response property for the response status `402`
  - added the new `freshness_ceiling_unsatisfied` enum value to the `error/reason` response property for the response status `403`
  - …12 more
- **2026-09-23** `8462acf80f8c`: 8 warning
  - added the new `export_expired` enum value to the `error/reason` response property for the response status `400`
  - added the new `export_expired` enum value to the `error/reason` response property for the response status `401`
  - added the new `export_expired` enum value to the `error/reason` response property for the response status `402`
  - added the new `export_expired` enum value to the `error/reason` response property for the response status `403`
  - …4 more
- **2026-09-22** `19386d595e05`: 1 breaking
  - the response property `total` became optional for the status `200`
- …earlier changes not shown

[Operation history](https://skmtc.dev/0xinsider/apis/0xinsider-api/changes/api/v1/market/:condition_id/holders/get.md)

---

[API](https://skmtc.dev/0xinsider/apis/0xinsider-api.md) - [All operations](https://skmtc.dev/0xinsider/apis/0xinsider-api/llms.txt) - [OpenAPI document](https://skmtc.dev/0xinsider/apis/0xinsider-api/revisions/2035b3316329?raw)
