---
title: "List observation-only sports-edge cohorts"
method: GET
path: "/api/v1/sports-edge-observations"
tags: ["Markets"]
---

# List observation-only sports-edge cohorts

`GET /api/v1/sports-edge-observations`

Pro-tier. Measures two explicitly observation-only Polymarket sports cohorts without changing or feeding GET /api/v1/sports-edge-signals: wider_holder measures pre-game holder piles outside the funded route's exact raw slate admission, including recent-flow rows rejected by its event, bucket, or total caps; in_play admits only provider-confirmed live games and fails closed when the provider live-board snapshot is stale or unavailable or holder/directional evidence is stale or unavailable. Every row carries observation_only=true, provider/holder freshness and completeness fields, and the response carries a required snapshot-wide operational/unknown-completeness degraded boolean plus an accountable per-sport funnel over a closed 25-value terminal-reason vocabulary. Omitted or blank category selects all 14 registered observation sport buckets, including Table Tennis and Pickleball; those two remain outside the funded sports projection. All category and all-sports cache scopes share one global observation provider-work admission, so distinct scope keys cannot multiply concurrent provider fanout. One absolute ~25s compute deadline covers cache coordination, board/universe and primary-slate membership reads, holder cache/provider work, price/metadata evaluation, and directional reads, leaving ~5s below the public router timeout for funnel reconciliation, cleanup, and response transport. A shared pre-holder stage deadline jointly bounds those database reads and board reconciliation to the smaller of 12s or half of the absolute budget remaining when that stage starts, preserving holder/post-holder opportunity; board reads use bounded fair waves and cold holder admission gives each represented canonical sport one row before any sport repeats. Single-flight refresh contention, global provider-work admission contention, absolute deadline exhaustion before a usable cache, or pre-holder stage expiry before a usable stored universe or primary-slate membership result returns 503 with error.reason=read_model_warming. Category-resolution SQL errors, Redis coordination failures, observation-universe SQL errors, and primary-slate membership query failures return 500 internal_error instead. Once a usable universe exists, later operational or unknown-completeness board, holder, or price/metadata failures are retained as explicit terminal reasons in a degraded 200 response with degraded=true: board_source_unavailable is a completed board-source failure, board_deadline_unavailable means live-board work missed either an internal configured-scope deadline or the outer fair-wave deadline; both classify only already-started rows, so for the upcoming source read funnel.sports[].board_upcoming_status instead; provider_unavailable is an attempted holder-provider failure, and holder_deadline_unavailable is holder-stage deadline exhaustion. Directional incompleteness is cohort-specific: a wider_holder row remains emitted with terminal wider_holder_emitted and directional_status=unavailable, while an in_play row fails closed with terminal in_play_directional_unavailable. capacity_limited records intentional bounded provider-work admission in the funnel and does not by itself set degraded=true. Healthy wider_holder requests may reuse a snapshot for ~180s; in_play never serves a cached observation snapshot older than ~30s, and degraded snapshots use ~30s. The ETag is a weak semantic validator over the stable page payload, including next_cursor, with request-specific meta excluded.

## Query parameters

- `cohort` 'wider_holder' | 'in_play', required
- `category` string
- `limit` integer
- `cursor` string

## Headers

- `If-None-Match` string

## Response `200`

Observation-only sports cohort with a snapshot-wide operational/unknown-completeness degraded verdict and accountable funnel

- object
  - `object` 'list', required
  - `data` SportsEdgeObservation[], required
    - `condition_id` string, required — Raw Polymarket condition id.
    - `token_id` string, required — Provider-backed Polymarket CLOB token id for the piled outcome. Rows without a verified token terminate before emission.
    - `category` 'Basketball' | 'Football' | 'Baseball' | 'Hockey' | 'MMA' | 'Boxing' | 'Soccer' | 'Cricket' | 'Golf' | 'Tennis' | 'Esports' | 'Racing' | 'Table Tennis' | 'Pickleball', required — Canonical sport bucket.
    - `raw_category` string, nullable, required — Raw provider category as stored.
    - `title` string, required — Provider-backed market title.
    - `event_slug` string, nullable, required
    - `event_id` string, nullable, required — Provider event id when available.
    - `parent_event_id` string, nullable, required — Provider parent-event id used as the first event-cap identity when available.
    - `game_start_time` string, date-time, required — Provider-backed kickoff time in UTC.
    - `observed_at` string, date-time, required — UTC instant when this row finished provider/holder evaluation.
    - `cohort` 'wider_holder' | 'in_play', required
    - `observation_only` true, required — Always true. This row must not be routed to an order executor.
    - `piled_side` string, nullable, required — Nullable provider-backed piled-outcome display label. When provider group context is unavailable, it may remain a bare Yes/No/Over/Under; do not use it alone as participant identity.
    - `piled_outcome_index` 0 | 1, required — Provider binary-column selector: 0 selects outcome_yes/token_id_yes; 1 selects outcome_no/token_id_no. It does not identify home/away or a participant. Use piled_side together with title/event context for display.
    - `backed_price` number, required — Provider-backed implied price for the piled outcome at observation time.
    - `sharp_pct` number, required — Piled-side graded-holder dollar concentration.
    - `backed_sharp_usd` number, required — Raw graded-holder USD on the piled outcome.
    - `s_count` integer, required
    - `a_count` integer, required
    - `b_count` integer, required
    - `graded_holders` integer, required — Piled-side S/A/B holder count.
    - `top_grade` 'S' | 'A' | 'B', required
    - `smart_score` number, required — Canonical signed holder-pile score.
    - `volume` number, required — Strictly positive stored market volume in USD. Missing, zero, or non-finite volume terminates as invalid_market and is never emitted as an observation.
    - `conviction_score` number, required — Grade-weighted holder-pile score before directional enrichment.
    - `provider_read_source` 'cached' | 'live', required — Whether the provider holder page came from the shared cache or a live provider read.
    - `holder_scan_complete` boolean, required — True only when neither provider outcome holder page hit the top-100 scan bound. False means the pile is a positive lower bound and cannot satisfy a future capital-promotion gate.
    - `holder_snapshot_at` string, date-time, nullable, required — Proven provider holder observation time. A warm cache hit uses only the original provider completion time from its companion metadata, never cache-read time. Null, malformed, future, or stale holder time fails in-play closed.
    - `directional_status` 'available' | 'unknown_ungrouped' | 'unknown_stale' | 'unavailable', required — Truthful state of the cross-market directional read. A wider_holder row can remain emitted with unavailable and terminal wider_holder_emitted; in_play fails closed instead and terminates as in_play_directional_unavailable.
    - `one_way_holder_count` integer, nullable, required
    - `hedged_holder_count` integer, nullable, required
    - `one_way_graded_usd` number, nullable, required
    - `directional_confidence` number, nullable, required
    - `directional_rank_score` number, required — Default cohort ordering key: conviction_score * (1 + 0.25 * directional_confidence), or conviction_score when confidence is null.
    - `rank` integer, required — 1-based rank within this observation cohort and snapshot.
  - `has_more` boolean, required
  - `next_cursor` string, nullable, required
  - `snapshot_as_of` string, date-time, required — Completion time of the shared observation snapshot pinned by the cursor.
  - `degraded` boolean, required — True when an operational failure or unknown provider-board, holder, directional, reconciliation, or internal completeness state made this snapshot partial. This is snapshot-wide and can retain degradation that the funnel's one-terminal-per-input accounting cannot represent. Intentional bounded capacity_limited rows remain fully accounted in the funnel and do not by themselves set this field.
  - `funnel` SportsEdgeFunnelReport, required — Per-sport accountable funnel for the full observation snapshot, returned on every page.
    - `sports` SportsEdgeSportFunnelReport[], required
      - `sport` 'Basketball' | 'Football' | 'Baseball' | 'Hockey' | 'MMA' | 'Boxing' | 'Soccer' | 'Cricket' | 'Golf' | 'Tennis' | 'Esports' | 'Racing' | 'Table Tennis' | 'Pickleball', required
      - `board_input` integer, required — Unique condition ids independently visible on the provider-first sports board.
      - `board_live_available` boolean, required — Whether the always-applicable live-board source completed as available. False can mean a completed source failure (board_source_unavailable) or live-board work missing an internal configured-scope deadline or the outer fair-wave deadline (board_deadline_unavailable); inspect terminals to distinguish them.
      - `board_upcoming_configured` boolean, required — Whether a provider-backed upcoming-board source is configured and applicable for this sport. False means not applicable, not provider failure.
      - `board_upcoming_available` boolean, required — Whether every configured upcoming-board scope completed as available. False with board_upcoming_configured=false means not applicable. When board_upcoming_configured is true and this flag is false, read board_upcoming_status for the cause (it reads unknown, i.e. no recorded cause, only on a snapshot cached before that field existed, which self-clears within one TTL): the board_source_unavailable and board_deadline_unavailable terminals are assigned only to already-started rows (the live half) and are structurally 0 for the upcoming source, so they never explain this flag.
      - `board_upcoming_status` 'unknown' | 'not_configured' | 'available' | 'source_unavailable' | 'cold_unavailable' | 'deadline_unavailable', required — Why the upcoming-board source is (un)available. Board supply is one canonical-sport union: the bare category owns live truth and every routable league scope contributes upcoming rows; folded leagues without a configured board (currently NCAAB, NCAAF, and CFL) are not in the upcoming union. available: every configured scope completed truthfully (a successful empty schedule still counts). source_unavailable: composition failed before a truthful union; resolved scope readers turn half failures into cold_unavailable, so fresh producers are whole-union identity reconciliation or a registry contract failure and carry zero rows. cold_unavailable: every scope completed but at least one reported its upcoming half unavailable because no servable entry was inside the stale-serve bound and the background warm did not land in time; it is not by itself proof of a provider outage. deadline_unavailable: an internal configured-scope deadline or the outer bounded fair wave expired. An internal deadline may retain healthy bare-category or sibling-scope rows; the outer wave records zero rows. These upcoming fields do not describe league live-membership availability. If the bare-category live scope fails, all live rows are dropped even when a league scope completed, because the bare category is the sole live-truth owner. not_configured: no upcoming scope applies to the sport. unknown: exactly one cause -- a snapshot cached before this field existed whose legacy flags recorded an unavailable-but-configured half without saying why. Every freshly computed snapshot reports a concrete status, and a legacy available or not-configured row is reconstructed exactly, so unknown self-clears within one TTL. board_upcoming_available is exactly board_upcoming_status == available.
      - `board_upcoming_unavailable_scopes` string[], required — Configured upcoming scopes that did not complete as available. Values are category, a provider league tag slug (nfl, nba, wnba, nhl, or mls), registry when the compiled scope/projection contract drifted, union when cross-scope identity reconciliation failed, or wave when the outer fair-wave deadline expired before scope-level evidence returned. Empty means no unavailable upcoming scope was identified; this includes healthy/not-configured rows and a legacy cached row. Observation league scopes are upcoming-only and perform no live-membership read.
      - `input` integer, required — Stored-universe rows plus provider-board rows missing from storage.
      - `terminals` object, required — Sparse counts over the closed 25-value terminal vocabulary: outside_horizon, resolved, provider_closed, provider_excluded, invalid_market, missing_token, missing_stored_market, not_provider_live, board_source_unavailable, board_deadline_unavailable, primary_slate_candidate, zero_indexed_holder_research, capacity_limited, provider_unavailable, holder_deadline_unavailable, holder_computation_unavailable, holder_scan_incomplete, no_current_graded_holder, split_holder_pile, price_unavailable, wider_holder_emitted, in_play_emitted, in_play_stale_observed, in_play_directional_unavailable, or internal_unclassified. primary_slate_candidate means exact admission by the funded route's raw shared slate query before provider/holder enrichment; recent-flow rows rejected by its event, bucket, or total caps remain eligible for wider_holder measurement. capacity_limited is intentional bounded provider-work admission, is fully accounted here, and does not itself set degraded=true. board_source_unavailable means a completed board source was unavailable; board_deadline_unavailable means live-board work missed either an internal configured-scope deadline or the outer fair-wave deadline; provider_unavailable means an attempted holder-provider read failed; holder_deadline_unavailable means holder cache/provider work missed the absolute request deadline; holder_computation_unavailable means post-holder provider or DB-backed price/metadata evaluation was unavailable.
      - `terminal_total` integer, required — Sum of every sparse terminal count.
      - `reconciled` boolean, required — True exactly when input equals terminal_total.
  - `meta` ResponseMeta, required
    - `request_id` string, required — Unique request ID (req_ prefix).
    - `cached` boolean, required
    - `cache_age_s` integer, nullable — Cache age in seconds, null if not cached.
    - `cost` integer, required — Advisory request weight (relative compute cost). 1 for simple reads; higher for heavier endpoints. Not a credit/price.
    - `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 smart-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 smart-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'
    - `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 slate 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 sports-edge observation payload.
- `400` — Bad request. On this cursor-paginated route a 400 has TWO distinct causes; branch on error.reason. (1) error.reason="cursor_expired" (with error.param="cursor"): the pagination cursor was invalidated by an upstream data change mid-walk (e.g. the ranking snapshot behind the page refreshed). It is NOT a malformed parameter and NOT a reason to stop: recovery is mechanical -- re-request the first page and walk forward again. There is deliberately no Retry-After and no error.retry_at, because waiting changes nothing. (2) no error.reason: an ordinary invalid request parameter -- check error.param when present, otherwise error.message. Both carry error.code="bad_request" (a FROZEN contract value), so error.reason is the discriminator.
- `401` — Missing or invalid API key
- `402` — Active Pro subscription required
- `403` — Account access denied
- `408` — Request exceeded the server's 30-second transport timeout. The timeout response has an empty body because it is generated before handler-level JSON error shaping.
- `423` — Account is locked
- `429` — Rate limit exceeded. Two 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 before any item work is done.
- `500` — Unexpected server error
- `503` — Service unavailable. On this route a 503 has TWO distinct causes; branch on error.reason. (1) error.reason="read_model_warming": the requested endpoint cannot serve its read model yet. Exact causes are endpoint-specific and can include a cold or contended refresh or a dependency that prevented refresh; consult that endpoint's contract and do not infer dependency health from this shared reason. This is endpoint-local unavailability, not rate limiting: retry only this route after Retry-After (or error.retry_at), and do not feed it into a rate-limit backoff shared with other endpoints. (2) no error.reason: the Redis-backed authenticated rate limiter is unavailable and the middleware failed closed; Retry-After is the seconds until it probes Redis again. Both carry error.code="rate_limit_unavailable" (a FROZEN contract value, so it cannot be split per cause) and X-Request-Id -- which is why error.reason, not error.code, is the discriminator.

---

[API](https://skmtc.net/0xinsider/apis/0xinsider-api.md) · [All operations](https://skmtc.net/0xinsider/apis/0xinsider-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/0xinsider/0xinsider-api/versions/918e082b045a/schema)
