---
title: "Replay historical large trades"
method: GET
path: "/api/v1/large-trades/history"
tags: ["Large trades"]
---

# Replay historical large trades

`GET /api/v1/large-trades/history`

Returns historical large trades from local whale_alerts rows, not request-time provider fetches. Filter by condition_id, trader, category, minimum grade, persisted suspicion, platform, and RFC3339 from/to windows. All filters are pushed into SQL before LIMIT, every request uses SQL-backed limit + 1 pagination, and results are ordered newest first by traded_at desc, id desc. Metadata exposes local_replay source and best_effort completeness. POINT IN TIME: signal_score, trader.grade and the min_grade filter carry today's values on every row however old, so a backtest that selects by them selects wallets on what they did after the trade. The point-in-time fields are recorded_signal_score (from 2026-08-03T11:59Z; null before, and never backfilled, because the trader statistics it reads at insert were not kept for older rows) and trader.grade_at_trade with trader.grade_at_trade_status (from 2026-09-19T23:00Z; unknown before). CAPTURE RULES changed over the archive's life: rows before 2026-02-02 are sparse (at most a few hundred a month); from 2026-02-02 the floor was 3,000 USD (1.4% of rows through 2026-07-05 are smaller) and trades at any price were kept; from 2026-07-06 a trade is kept at 10,000 USD or more (1,000 USD in earnings markets) and only when priced below 0.97 (0.99 in earnings markets). Pass min_size=10000 for one size rule across the whole range; monthly row counts still follow the sports calendar. Until 2026-07-17 one match could be stored twice, once per wallet: from 2026-05-01 to 2026-07-17, 27.7% of rows at 10,000 USD or more share a transaction and market with another stored wallet, almost always a Yes buyer and a No buyer filled against each other. From 2026-07-18 a row is the taker's side only. Before 2026-05 the transaction hash is mostly absent, so the share cannot be measured there. From 2026-09-23 a fill must ALSO be at least 0.1% of its market's recorded traded volume, Polymarket's own share count, so a $10,000 print that lands in a market which has already traded tens of millions of shares is no longer kept; rows written before that date were not re-filtered. The response adds a top-level `data_quality` object beside `data`, grouping alert, trade, trader, ranking, market, and volume fields by their database writer. `whale_alerts.inserted_xid` is reported as unknown because it is a transaction identifier rather than a timestamp. Its stored clocks are part of the ETag; `meta` continues to hold transport cache facts.

## Query parameters

- `limit` integer
- `cursor` string
- `min_size` number
- `condition_id` string
- `trader` string
- `category` string
- `min_grade` 'S' | 'A' | 'B' | 'C' | 'D' | 'F'
- `suspicious_only` boolean
- `platform` 'polymarket' | 'all'
- `from` string, date-time
- `to` string, date-time
- `min_market_volume_share` number
- `sort` 'recent' | 'market_volume_share'

## Headers

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

## Response `200`

Historical large trade replay

- object
  - `object` 'list', required
  - `data` LargeTrade[], required
    - `id` string, required — Prefixed ID (wt_...).
    - `traded_at` string, date-time, required
    - `size_usd` number, required
    - `side` 'BUY' | 'SELL', required
    - `outcome` string, nullable, required — Traded outcome label (e.g. "Yes"/"No"/team name), resolved provider-first from the trade's outcome_index against market_canonical (index 0 -> yes, 1 -> no). Distinct axis from side (BUY/SELL): side is the trade direction, outcome is which leg was traded. null for multi-outcome (outcome_index >= 2) or unsynced markets, and for a Polymarket trade recorded before 2026-04-02T00:00:00Z, whose stored outcome_index is not trusted (a defaulted 0 for about a third of those rows; the side is unknown, not defaulted).
    - `token_id` string, nullable, required — The Polymarket CLOB token id (ERC1155 asset id, decimal string) for the traded outcome; null when unavailable (e.g. unsynced markets) and for a Polymarket trade recorded before 2026-04-02T00:00:00Z, where the traded side is unknown.
    - `price` number, required
    - `review_score` number, required — Current 0.0–1.0 review score, computed at request time from the trade's size, the trader's win rate today, a bonus when a trader with a win rate above 55% trades at a price below 30¢, and the trade's age now. A higher score means read this trade first; it does not measure edge or predict an outcome. On a historical row it is today's view of the trade, not what a reader saw then; use recorded_review_score for that. Canonical since #16311; signal_score carries the same value.
    - `signal_score` number, required — Current 0.0–1.0 review score, computed at request time from the trader's win rate today and the trade's age now. Deprecated (#16311): `review_score` is the canonical spelling and carries the same value; this key stays on the wire.
    - `recorded_review_score` number, nullable, required — 0.0–1.0 review score written once when the trade row is inserted, from the trader's statistics at that moment. Populated from 2026-08-03T11:59Z; older rows return null and are never backfilled, because a backfill could only read today's statistics. If a trade is added later, its time-sensitive recorded score reflects that delay. Canonical since #16311; recorded_signal_score carries the same value.
    - `recorded_signal_score` number, nullable, required — 0.0–1.0 review score written once when the trade row is inserted; null before 2026-08-03T11:59Z. Deprecated (#16311): `recorded_review_score` is the canonical spelling and carries the same value; this key stays on the wire.
    - `suspicion_score` integer, nullable, required — Persisted live suspicion score from the scorer. Null when the row has no persisted score.
    - `suspicion_track` 'whale' | 'fresh_conviction' | 'sliced_position', nullable, required — Persisted scorer track. Null when a legacy row has no stored track label.
    - `market_volume_share` number — This fill's size relative to its market: size_usd divided by a market volume figure recorded at or after the trade, so the value always falls between 0 and 1 inclusive. A $10,000 fill is 0.00005 of a $200M market and 0.125 of an $80,000 one, which size_usd alone cannot distinguish. Absent when no volume figure recorded at or after the trade is available; never 0 as a stand-in and never capped at 1, because a denominator we cannot trust publishes nothing rather than a trimmed number. A market's volume keeps growing, so the same trade reports a smaller share as the market trades on.
    - `trader` object, required
      - `id` string, required
      - `address` string, required
      - `username` string
      - `grade` string — The trader's grade today, on every row however old. For what the grade was when the trade happened, read grade_at_trade.
      - `grade_at_trade` 'S' | 'A' | 'B' | 'C' | 'D' | 'F', nullable, required — The grade the trader held when the trade happened, from recorded grade history (recorded from 2026-09-19T23:00Z). Null unless grade_at_trade_status is graded. Never today's grade projected backward.
      - `grade_at_trade_status` 'graded' | 'ungraded' | 'unknown', required — graded: grade_at_trade holds the recorded grade. ungraded: the trader was recorded without a grade at that moment. unknown: no record covers the moment, which is every trade before 2026-09-19T23:00Z and a trade that fell between a grade change and its confirmation. unknown never means ungraded.
    - `market` object, required
      - `id` string, required
      - `condition_id` string, required
      - `title` string, required
      - `slug` string
      - `category` string — Provider-backed market_canonical category.
  - `data_quality` DataQuality, required — Compact data age and coverage for a response body, always present on the operations that publish it. Read status and as_of to decide whether to use the body at all, and field_groups to see which part is weak. Everything here comes from stored observation clocks, so a cached body reports the same ages a freshly computed one does: meta.cached and meta.cache_age_s stay the only transport-time facts and neither makes this block newer. The per-field audit object is still available through expand=trust; this is the default summary of the same question.
    - `status` 'fresh' | 'partial' | 'unknown' | 'untracked' | 'unavailable', required — fresh when every group is fresh, unavailable when every group is unavailable, and partial in every other case.
    - `as_of` string, date-time — The oldest as_of among the groups that carry one: the age of the weakest clock this body rests on. Omitted when no group carries a clock.
    - `field_groups` DataQualityGroup[], required — One entry per field group. Entries may be added in later releases, so match on group rather than on position or length.
      - `group` string, required — Stable snake_case group name. Names are additive across releases, so match on the ones you know and ignore the rest.
      - `owner` string, required — The table and column that write this group, named so the verdict can be audited (for example trader_rankings.computed_at).
      - `status` 'fresh' | 'partial' | 'unknown' | 'untracked' | 'unavailable', required — fresh: served, and as_of carries this group's real observation or computation clock. partial: some of the group's fields are served and some are missing. unknown: served, and this read has no clock for it, so no age may be inferred. untracked: 0xinsider does not track this group for this subject, by design. unavailable: the group could not be served. New values may be added; treat one you do not recognize as unknown. fresh means the group is tracked and clocked, not that it is inside any particular tolerance: compare as_of against your own.
      - `as_of` string, date-time — When this group's values were observed or computed. Omitted whenever the read cannot measure it, and never filled with the serialization time, the cache time, or another group's clock.
      - `reason` string — Why the status is not fresh. Omitted when it is.
  - `has_more` boolean, required
  - `next_cursor` string
  - `total` integer — Total matching rows when the read model exposes a count; the key is absent when it does not.
  - `meta` LargeTradeHistoryMeta, 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; the key is absent when the response was not cached.
    - `source` object, required
      - `kind` 'local_replay', required
      - `table` 'whale_alerts', required
      - `provider_fetch_at_request_time` false, required
    - `completeness` object, required
      - `status` 'best_effort', required
      - `reason` string, required — Explains that local replay completeness can vary by market and time window.

## 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
- `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.
- `423` — Account is locked
- `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` — Redis-backed authenticated rate limiter unavailable; retry after the per-process outage cooldown

## Changes

- **2026-09-23** `9d1ba37bd5e3` — 1 info
  - added the required property `data_quality` to the response with the `200` status
- **2026-09-23** `7e57bd9dc8b5` — 2 info
  - added the new optional `query` request parameter `min_market_volume_share`
  - added the new optional `query` request parameter `sort`
- **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** `a885dfbbeaff` — 4 info
  - response property `data/items/recorded_signal_score` deprecated
  - response property `data/items/signal_score` deprecated
  - added the required property `data/items/recorded_review_score` to the response with the `200` status
  - added the required property `data/items/review_score` to the response with the `200` status
- **2026-09-23** `1901bfe7e90a` — 2 info
  - api tag `Large trades` added
  - api tag `Whale Trades` removed

[Full history](https://skmtc.dev/0xinsider/apis/0xinsider-api/changes/api/v1/large-trades/history/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)
