# Cross-venue mapping contract

Updated September 20, 2026. Base URL: `https://api.depthfeed.com`. Authenticate with `X-API-Key`. This document describes public fields and current coverage, not a commitment to future coverage. See [the integration guide](/api-integration.md) for paging, plan limits and history.

## Sports identities

Retrieve `GET /v3/sports/markets`, `GET /v3/sports/markets/{condition_id}`, or the paginated `GET /v3/sports/games/{game_slug}/markets`.

| Field | Meaning and safe use |
|---|---|
| `condition_id` | Stable catalogue-record identity for upserts. A grouped Kalshi moneyline record can use an event identity; its tokens identify the underlying contracts. Never upsert by a mapping key. |
| `game_slug` | Venue-specific fixture handle. Not a cross-venue identifier. |
| `cross_venue_key` | Supported fixture or future subject identity. Does not assert payout equivalence. |
| `fixture_participants[]` | Canonical `participant_key` plus display `name`, independent of market kind. Empty when fixture participants cannot be established. |
| `kind` | Original venue-native category, unchanged. |
| `market_family`, `market_period`, `market_family_version` | Additive kind classification. Unknown kinds have a null family/period. Does not supply an absent line, player, side or settlement rule. |
| `outcomes[]`, `tokens[]` | Plain-string arrays, aligned by index within this market only. Retain order. |
| `contract_mapping.outcomes[]` | One entry per exposed token: `index`, `token_id`, `label`, nullable `participant_key` and nullable confirmed outcome key. |
| `contract_mapping.candidate_key` | Fixture-contained subject comparison group for supported moneyline, line or binary propositions; mutable after corrections, and not proof of equivalent payouts. |
| `cross_venue_market_key` | Confirmed contract identity. Exactly equal to `contract_mapping.cross_venue_market_key`; both populate or clear together. |
| `contract_mapping.status`, `reason_codes` | Contract-review state, independent of whether the fixture or participants are identified. |

Some Kalshi moneyline rows group two independent YES contracts, whereas other rows expose one contract. Row counts therefore are not counts of identical contract objects across venues. A fixture's participants do not tell you which participant a generic Yes/No or Over/Under outcome means. A Kalshi row with one YES token remains one token; NO is its complement, not automatically the opposing team. Three-way soccer, draw-no-bet and two-team moneylines are not interchangeable. Never join outcomes by array position across venues.

The additive identity schema is `fixture-participants-v1`. No conversion of native outcomes/tokens into objects is part of this release. Consumers should tolerate additional object fields and unknown kind labels while preserving the plain-string arrays.

## Kind crosswalk

`scope=future` records do not inherit a single-game period from a generic native kind: both `market_period` and `contract_mapping.period` are null. This includes season totals and tournament outrights. Their family can still be known; a known family does not establish a game fixture or settlement equivalence. The September 19 period correction republishes affected existing records through catalogue deltas.

Fetch `GET /v3/sports/market-families` or [market-families.json](/market-families.json). The registry is versioned `sports-families-v1` and contains every currently published alias group. Examples:

| Native kinds | Family |
|---|---|
| `anytime_touchdown`, `anytime_touchdowns` | `anytime_touchdown` |
| `first_td`, `first_touchdowns` | `first_touchdown` |
| `passing_yards`, `player_passing_yards` | `player_passing_yards` |

Use the market row's returned period together with the family. Native first-quarter/first-half labels override generic full-game kind classifications; conflicting or unsupported subdivisions return a null period. The static kind crosswalk alone is insufficient when a venue uses a generic kind label. First-half and full-game categories are distinct. A family groups labels only: it does not make two lines, participants or payout conditions equivalent. Unknown labels stay unknown rather than being guessed.

## Practical interim comparison

1. Group market rows by non-null `cross_venue_key`.
2. Compare `market_family` and `market_period`; reject unknown or differing values for automated matching.
3. Align labelled team outcomes using `contract_mapping.outcomes[].participant_key`, not label spelling or index. Keep the native token association.
4. For supported spreads, totals and team totals, inspect `contract_mapping.contract_spec` and each outcome's `side`. Missing specifications, integer/push lines, split lines and props still require independent source review. Leave unsupported pairs unresolved.
5. Review settlement rules, including period, overtime, ties, postponement, cancellation, result source, finality, abandonment, forfeits, venue changes and other material rules.

These steps support candidate research. They are not a guarantee of equivalent payouts. Automated confirmed joins require non-null confirmed keys, `status=matched`, `terms_verified=true`, fresh evidence and a matched counterpart in `comparisons[]`.

## Candidate keys as warehouse grouping keys

A non-null candidate key is a supported **subject grouping key**, not a unique market ID or a settlement-equivalence certificate. You may join candidate groups across venues while carrying your own independently reviewed settlement decision. Do not call that a confirmed DepthFeed match. Keep each native `condition_id` and `token_id`; the relationship can be many-to-many.

Fixture containment is part of key construction: the complete `cross_venue_key` is hashed into every candidate key. Line keys also include family, period, metric, canonical subject, exact normalized line, comparison and push model. The `ml-candidate-v1` namespace is restricted to the supported full-game, two-participant moneyline model; its family/period are implicit in that namespace, not separate fields inside its existing hash. `binary-candidate-v1` includes the explicit proposition specification. The builder rejects a candidate group spanning fixtures. Keys are opaque, versioned hashes; retain the fixture and specification for validation instead of parsing the hash.

**Candidate keys are not immutable.** Correcting a fixture, participant identity, family, period, scoring metric or line can recompute or revoke a key. A canonical participant correction can also change the spread reference participant and reverse its sign and sides. Cosmetic labels that leave the normalized subject unchanged need not change the key. Published corrections advance the affected market's `updated_at`/`updated_ms`; the source and mapping publication pipeline is asynchronous. A key may temporarily be null while changed inputs are revalidated. Evidence expiry alone does not erase a valid candidate.

Upsert current rows by `condition_id`, never by candidate key. When a key changes or clears, remove the old current membership before adding its replacement. For reproducible historical joins, retain the previously received row versions and their observed/update timestamps; overwriting current membership cannot preserve an old historical association. Mapping corrections do not rewrite the timestamps of historical order-book records.

### Native market multiplicity

`contract_mapping.candidate_group` exposes `venue_counts`, native `condition_ids` grouped by venue, `requires_selection`, and `selection_policy=preserve_native_markets`. It describes the mapper's published group, not a promise that every source listing is known. A missing group object means its publication is not yet available. Counts can change as discovery and mapping progress; consume metadata deltas.

There is no authoritative-row selector. In the reviewed Green Bay–Minnesota example, Polymarket listed separate main-event and player-props-event markets with distinct condition IDs and token IDs, even where questions and rules matched. Both remain native markets. Preserve both books and select explicitly for the intended analysis; use a review queue if your workflow requires one market per venue. Do not discard one by array order, slug, or highest volume, or sum their prices as though they were the same token.

### Outcome and position joins

For supported moneyline candidates, join labelled outcomes on `participant_key` within the candidate. For line candidates, join on `side=above|below` within the identical candidate/specification. Team totals bind their team in `contract_spec.participant_key`; generic Over/Under outcome labels do not themselves name a team. For supported binary candidates, join on `side=yes|no` relative to the explicit predicate in `contract_spec`.

A one-token Kalshi row exposes one native YES contract. Its NO position is the complement of that same contract, not a second missing token. For a supported half-unit line, that position corresponds to the opposite threshold side during ordinary scored settlement. Represent a position as `(venue, token_id, YES|NO)` if you need both sides; do not invent a NO token ID or append one to the API arrays. This position relationship does not erase differences in void, cancellation, postponement or other payout rules. A grouped two-token Kalshi moneyline row instead contains two independent YES contracts; do not treat its array entries as YES and NO of one contract.

## Current contract-matching scope

Moneyline candidates are evaluated across leagues when fixture identity and two participant-labelled outcomes are established. Candidate coverage is conditional, not universal. The `sports-lines-v1` path adds source-bound comparisons for spreads, totals and team totals. It uses exact native half-unit lines, explicit period, scoring metric, canonical subject and outcome side. Supported source forms include points, runs and goals; explicit full-match tennis games or sets; esports series games or maps; and native discrete corners, touchdowns, made field goals and total offensive yards. These metrics remain distinct. Source wording, native strike and subject must agree. First/second-half, first-through-fourth-quarter, first-five-innings and individual first-through-ninth-inning forms are recognized. The registry includes q1–q4 spread, total and team-total kinds; candidate extraction still requires exact source evidence. The native kind `baseball_team_first_five_total` means combined first-five runs when its rules explicitly establish that total, not a single-team total. Integer/push and fractional quarter-unit/split lines, unknown metrics, ambiguous subjects and unsupported source formats remain unresolved. Futures and player props are outside this line matcher. The same reviewed-evidence gate applies; implementing a matcher does not make incompatible venue rules equivalent.

### Line specification

`contract_spec` contains `family`, `period`, `metric`, nullable subject `participant_key`, decimal-string `line`, `comparison` and `push`. Outcome entries additionally carry `side=above|below`. Spreads use the lexicographically first canonical fixture participant as the reference: `line` is that participant's required scoring margin. A reversed native selection can therefore have `side=below`; never infer side from token index across venues. Totals use the combined score; team totals name their subject separately from the generic Over/Under labels.

For example, a spread specification with subject `mlb:baltimore-orioles`, line `-1.5` and outcome side `below` describes Baltimore's margin below -1.5 runs. A Kalshi YES leg and the corresponding Polymarket outcome can share a candidate while their cancellation or postponement rules still differ. Source line agreement is not settlement verification.

Normalized specifications use half-unit boundaries (`push=not_possible_half_unit`). An explicitly discrete native `N+` proposition can normalize to `line=N-0.5` only when its integer count, exact native greater/greater-or-equal strike, metric and subject agree. For example, at least 10 corners becomes above 9.5 corners. This does not implement push-bearing integer or split lines. A single Kalshi YES token stays a single token. No synthetic NO token is added. Source or catalogue input changes invalidate prior evidence; a confirmed comparison cannot outlive either side's evidence deadline.

### Explicit binary propositions

`sports-binary-v1` adds source-bound candidates for recognized safety, extra-innings, first-inning-run, soccer both-teams-to-score and first-team-to-score propositions. It uses an explicit `contract_spec` containing family, period, metric, optional subject participant and predicate. It does not approve settlement equivalence. Only source templates positively identified by the parser receive keys; unsupported wording or missing evidence remains unresolved.

Do not infer the predicate from `kind`: a reviewed Polymarket `nrfi` record asks whether a run **is** scored in the first inning, and its YES token means at least one run. Its normalized family is `first_inning_run`, period `first_inning`, predicate `at_least_one_run`. Supported binary outcome `side=yes` means the explicit predicate occurs; `side=no` means it does not. A differently worded or inverted proposition is not silently assigned the same identity.

Period matters for soccer binaries. Regulation plus stoppage time and an entire game including extra time are distinct specifications. First-team-to-score also binds the named participant: the other team scoring first and no team scoring are not the same proposition. The current explicit first-team-to-score parser accepts the verified Kalshi source form; unsupported source forms do not get a guessed cross-venue counterpart.

The approved settlement-profile registry currently contains **zero approved equivalent pairs**. Confirmed market/outcome keys therefore remain null. A successful API response or a non-null candidate key does not change that limitation. No nonzero-coverage date or broader-kind release date is committed.

Source collection and diagnostic clause extraction run automatically. Confirmed equivalence requires a complete reviewed profile tied to the exact source-evidence hash. Unknown prose is not approved automatically. Changed inputs, changed evidence or expiry invalidate eligibility. A source outage produces missing/stale evidence, not a fabricated match.

Kalshi source contracts also carry a `series` descriptor when the governing PDF is available: `contract_terms_url`, `contract_terms_sha256`, `settlement_sources` and `document_scope`. The archived PDF hash is part of the evidence hash. Current series terms are not proof that the same document governed a historical contract; a confirmed review must explicitly establish applicability. Reviews must cover all eleven rule dimensions above. A missing governing document cannot produce an approved Kalshi mapping. Changes are detected during source refresh, not instantaneously; evidence deadlines still apply.

Fetch `GET /v3/sports/markets/{condition_id}/contract` for the current mapping and stored source evidence. `evidence=null` means nothing has been stored. Otherwise inspect `checked_ms`, `valid_until_ms`, `expired` and `input_matches_current_market`. Evidence can contain empty `contracts` after a failed collection. Returned source text can be stale or insufficient and does not itself establish equivalence. Collection uses exact-ID batches and two persistent rotations: four active-fixture partitions for all unresolved supported markets with a known start time, including past games and distant future fixtures, and 32 historical backfill partitions. Active collection retains all counterpart rows for each selected fixture and begins unresolved evidence renewal 15 minutes before expiry. This does not extend the evidence deadline. Work is bounded and venue requests remain throttled. Backfill is ongoing; collection does not guarantee complete venue coverage or a fixed refresh latency.

The evidence object also exposes `last_attempt_ms`, `retry_after_ms` and `error_code`. On successful collection, `last_attempt_ms` equals `checked_ms` and the error/retry fields are null. A transient renewal failure can retain the last source text only when the market inputs still agree. That retained text is explicitly expired (`valid_until_ms=0`), keeps its original `checked_ms`, and cannot authorize confirmed equivalence. `last_attempt_ms` identifies the failed attempt; `retry_after_ms` identifies when another attempt becomes eligible, not a guaranteed completion time. Changed inputs or source identity validation failures do not reuse the old text. Previously collected evidence remains eligible for renewal even if a counterpart disappears.

## Field-by-field correction delivery

| Field or event | Publication behavior |
|---|---|
| Persisted fixture-key correction or revocation | Affected market records are republished with a newer `updated_at`/`updated_ms`. Preserve null revocations. |
| Persisted kickoff or participant-identity correction | Same publication queue and delta mechanism. |
| Published contract mapping, evidence renewal or revocation | Republished through the catalogue delta. |
| Evidence reaches `evidence_valid_until_ms` | A wall-clock deadline, not a database write. The API clears confirmed eligibility at read time. Clients must expire eligibility locally even before a later delta. Candidate and participant identities remain available. |
| New schema field or classification release | Backfill is release-specific. The September 19 participant/family release republishes existing sports records; a future field must not be assumed to do so without an explicit release statement. |
| Historical book correction | Not a market-metadata delta. Re-fetch affected history separately. |

The September 19 publication mechanism supersedes earlier support statements that fixture corrections could change without advancing `updated_at`. New fixture-bearing discovery rows prepare their fixture identity and condition links before catalogue publication; their timestamp is assigned at the actual write. Ambiguous or unsupported source identities can still remain null. Persisted corrections are asynchronous; a scheduling interval is not a completion guarantee. Evidence expiry remains the explicit exception above.

Poll `GET /v3/sports/markets?updated_since=...` using epoch milliseconds, an ISO timestamp or a date. Keep the lower bound and filters fixed throughout pagination. Include resolved markets. Upsert whole rows by `condition_id`, retaining nulls. Advance from the saved poll-start time with overlap only after every page succeeds; do not advance to the largest timestamp seen during a moving traversal. Retry failed traversals from the prior checkpoint. A one-time schema backfill can legitimately return the entire existing catalogue through this delta path.

Treat key changes as revocations plus replacements, not as new market identities. Keep `resolved`, fixture identity and contract-review state separate. A market can be finalized with unknown mapping, or open with a valid fixture identity.

## Crypto subjects and rules

Crypto discovery uses `GET /v3/{coin}/markets` for Polymarket and `GET /v3/kalshi/markets?coin={coin}` for Kalshi. A key such as `btc-updown-15m-1789687800` describes an asset and scheduled interval. It does not establish settlement equivalence, and the sports `contract_mapping` object does not cover crypto.

Keys require positively identified native up/down products. Polymarket slugs must agree with the asset and interval. Supported stored slug forms include 5m, 15m and 4h; the presence of a duration alone is insufficient. Textual hourly/daily slugs, price bands, thresholds and non-directional Kalshi series are not assigned an up/down key. Kalshi's `KX{ASSET}15M` identifier supplies the scheduled New York close; captured-book timestamps are not the window definition. Ambiguous or malformed identity stays null.

The September 19 metadata audit found these shared 15-minute subject windows. Counts include listed future windows and are a snapshot, not a completeness guarantee. [Full venue/asset/interval coverage JSON](/crypto-mapping-coverage.json) includes the separate 5-minute and 4-hour Polymarket ranges.

| Asset | Shared windows | Earliest shared start (UTC) |
|---|---:|---|
| BNB | 16,569 | 2026-03-18 20:15:00 |
| BTC | 23,094 | 2026-01-08 02:45:00 |
| DOGE | 16,579 | 2026-03-18 20:00:00 |
| ETH | 22,503 | 2026-01-08 02:45:00 |
| HYPE | 16,567 | 2026-03-18 20:00:00 |
| SOL | 22,367 | 2026-01-09 00:45:00 |
| XRP | 19,568 | 2026-02-11 05:00:00 |

Historical coverage is venue-, asset- and interval-specific and can have gaps. Do not infer complete backfill from the earliest date, a shared key, or row counts. Enumerate the desired date window and verify captured history separately; metadata discovery with `include_uncaptured=true` is not proof of captured books.

Sampled Kalshi 15-minute contracts use CF Benchmarks and sampled Polymarket contracts use Chainlink. Both sampled rules award the positive side on equality, but that does not eliminate index, averaging-window or finality differences. Read each native contract's current rules before comparison. Do not extrapolate a sampled rule across every historical series.

Kalshi catalogue corrections support `updated_since` and expose `updated_at`; the materialized catalogue refreshes separately from ingestion. Keep native ticker as the upsert identity. Polymarket uses its native market identity and published update timestamps. Null crypto keys revoke prior subject assignments; hourly/daily price-band keys previously inferred from duration must not remain in a client mirror.

## Error handling

HTTP 200 means the request succeeded, not that all venues are complete or all mappings are confirmed. Inspect reason codes, evidence state and `meta.partial` where supplied. A failed catalogue-enrichment query returns 503 rather than masquerading as an empty successful page. Retry with bounded backoff and retain the last successful checkpoint. Follow opaque cursors until `has_more=false`.

## Repeated values and missing-data checks

Audit repeated values by field, venue, family and lifecycle state. `resolved=false` is independent of mapping review. A resolved record can lack a unique winning token (for example, a split payout or a single Kalshi YES contract that did not win); do not infer an unknown payout from that absence. Generic outcome participants may be null while the fixture participants are known. Confirmed equivalence keys remain null when no approved profile exists. These are distinct from a failed database read.

Player roster age, height and weight are null when unavailable, rather than fabricated zeros. Player metadata and Pinnacle-series database failures return 503; they must not be treated as successful empty datasets. Response assembly failure likewise returns an error instead of an empty HTTP 200. Legitimate zero scores and source identifiers are retained: for example, the team abbreviation `NAN` means Nantes, not a numerical NaN placeholder.

An upstream record can itself have an empty label. Preserve the token association and flag it for source review; do not fill it from an unrelated event title. Data-quality checks do not establish that every upstream record is complete.
