# DepthFeed API integration guide

Updated September 19, 2026. This guide covers the current integration contract for discovery, cross-venue sports matching and historical books. Examples use `https://api.depthfeed.com` and a key supplied through `X-API-Key` or `Authorization: Bearer`. A successful status code does not establish data completeness.

## Discover markets and choose the correct identifier

| Surface | Discovery | Identifier for book history |
|---|---|---|
| Sports, both venues | `/v3/sports/markets` | An entry in `tokens[]` |
| Polymarket, any category | `/v3/polymarket/markets` | `asset_id` / outcome token |
| Polymarket crypto market | `/v3/{coin}/markets` | `market_id` for the market-level endpoint |
| Kalshi | `/v3/kalshi/markets` | Market ticker |
| Polymarket US, any category | `/v3/polymarket-us/markets` | Market slug |
| Limitless | `/v3/limitless/markets` | Market slug |
| PredictFun | `/v3/predictfun/markets` | Market ID |

Both `/v3/kalshi/markets` and `/v3/polymarket-us/markets` accept `category` and `exclude_category` as comma-separated lists of the venue's own labels, matched case-insensitively and combinable. On Kalshi the default listing is limited to markets with captured depth; `include_uncaptured=true` returns the venue's full catalogue, and each row's `depth_captured` states whether a book series exists behind it. Both listings take `updated_since`; on Polymarket US it selects rows whose content changed since that time and each row carries `updated_at`.

Sports market lookup accepts `/v3/sports/markets/{condition_id}` and also resolves an outcome token to its parent market. Always use `tokens[]` for `/v3/sports/books/{asset_id}/history`. A grouped Kalshi moneyline has an event identifier as its `condition_id` and separate market tickers as its tokens; these are not interchangeable.

`outcomes[i]` and `tokens[i]` align within one market. Neither label nor index is a cross-venue outcome identity. Existing outcome labels and `kind` values remain venue-native; common spellings do not establish equivalent contracts.

## Sports identity and moneyline comparisons

The following fields answer different questions:

| Field | Meaning |
|---|---|
| `game_slug` | Venue-specific handle used by the game routes |
| `cross_venue_key` | Same supported fixture or future subject; not equivalent payout terms |
| `contract_mapping.candidate_key` | Supported fixture-contained subject group; mutable after corrections, potentially many-to-many, without a settlement-equivalence claim |
| `contract_mapping.outcomes[].participant_key` | Canonical participant corresponding to the supplied token |
| `cross_venue_market_key` | Confirmed contract key; null unless the mapping is `matched` |
| `contract_mapping.outcomes[].cross_venue_outcome_key` | Confirmed outcome key; null without confirmed contract equivalence |

Fixture participant identity now applies independently of market kind, across keyed fixtures that have identifiable participants. `fixture_participants[]` contains canonical `participant_key` and display `name`. `market_family`, `market_period` and `market_family_version` expose the [versioned kind crosswalk](/market-families.json). Unknown kinds retain their native `kind` and have a null family. These fields do not normalize a missing line, player or side.

Moneyline candidates are evaluated across leagues where both participant-labelled outcomes can be identified. Generic Yes/No and Over/Under are not team identifiers. Source-bound half-unit spread, total and team-total comparisons use sports-lines-v1, contract_spec and per-outcome side. Native periods override generic kind labels.  Explicit native count propositions can normalize to half-unit boundaries when their strike, metric and subject agree. Tennis games/sets, esports games/maps, corners, touchdowns, field goals and offensive yards retain distinct metrics. First-five totals are combined runs; individual inning scopes remain distinct. Integer/push and split lines, futures and props remain unsupported. Quarter spread/total/team-total kinds and explicit source-bound binary propositions are also supported where their evidence validates. See /mapping-contract.md for candidate-key changes, native market multiplicity, position joins, binary predicates and signed-spread semantics. The reviewed-profile registry currently has zero approved equivalent pairs, so confirmed keys are null. There is no committed date for broader verified matching.

`contract_mapping` is included in market listings and single-market and fixture responses. Its `cross_venue_market_key` is an identical alias of the top-level field. `GET /v3/sports/markets/{condition_id}/contract` adds the stored source evidence, its expiry and whether its inputs still match the market. Missing evidence is null; returned rule text alone is not proof of equivalence. `GET /v3/sports/market-families` returns the crosswalk.

Read the [field-by-field mapping contract](/mapping-contract.md) before implementing joins or correction polling.

| `status` | Client behavior |
|---|---|
| `matched` | A counterpart has the same reviewed terms. Join by the confirmed market and outcome keys; inspect the counterpart list. |
| `unmatched` | A known difference, unsupported scope or absent counterpart prevents a confirmed join. Read `reason_codes`. |
| `needs_review` | Evidence, participant identity or mapping availability is insufficient. Do not treat it as an equivalent contract. |

Comparisons cover period, overtime, ties, postponement, cancellation, result source, result finality, abandonment, forfeits, venue changes and other material rules. `terms` can contain nulls: diagnostic extraction is not proof of a complete rule review. A confirmed key requires `terms_verified: true`. `evidence_hash`, `evidence_sources`, `evidence_checked_ms` and `evidence_valid_until_ms`, when available, identify the evidence and its freshness. Expired evidence or changed market inputs cannot keep a confirmed key. Coverage will vary; the existence of these fields does not promise a confirmed counterpart for every moneyline.

The `/contract` evidence object reports `last_attempt_ms`, `retry_after_ms` and `error_code` separately from the observation's `checked_ms`. A transient renewal failure may retain old source text with an explicit expired deadline, preserving research identity while revoking equivalence eligibility. Do not interpret retained text or an HTTP 200 as fresh evidence. A retry deadline describes eligibility, not guaranteed availability.

`comparisons[]` identifies each candidate counterpart with its own status and reasons. A market can have one matched counterpart and another unmatched one. Never assume every entry shares the row's overall status.

The reported Rams–49ers pair illustrates a valid exclusion. Both rows share fixture `nfl-lar-sf-2026-09-10`; Kalshi's “Los Angeles R” and Polymarket's “Rams” align to `nfl:lar`, while “San Francisco” and “49ers” align to `nfl:sf`. Their postponement clauses differ: one specifies a 48-hour start window and a fallback settlement, while the other waits for completion. When fresh evidence is available, this difference can produce `unmatched` and `different_postponement`; otherwise read the current evidence-related reason codes. Neither state supplies a confirmed contract key.

Full source-rule text, when fetched, is available on the dedicated `/contract` endpoint rather than repeated in every catalogue row. Check its expiry and input-match flag; unknown conditions are not assumed to match.

### Row grain, unresolved markets and data-quality triage

One row is a catalogue market record. Some Kalshi moneyline rows group two independent YES contracts; other records expose one contract. A single fixture can produce separate moneyline outcomes, spreads, totals, team totals, props, line variants and historical condition records. Therefore counts grouped by `cross_venue_key` are not expected to match across venues. A large count difference is a review signal, not proof that one venue's rows are missing or duplicated.

`resolved=false` means the venue has not marked that market condition final. It does not mean that the cross-venue mapping is unresolved. Conversely, a market can be unresolved and still have a valid candidate fixture key, while a resolved market can have a null or revoked mapping. Keep these axes separate: `resolved`, `contract_mapping.status`, and the presence of `cross_venue_key` answer different questions.

For a quality audit, enumerate the relevant league, `scope`, and `kind` through `GET /v3/sports/markets`, then compare venue rows at the same normalized signature: fixture, period, participant, line, side, market kind and settlement profile. Use `contract_mapping.candidate_key` to form candidate groups and use `cross_venue_market_key` only for confirmed contract joins. Do not fill a null key from a slug, title, outcome-array position or a row-count ratio. Persist the returned `reason_codes` and evidence state so a later patch can revoke a previous mapping.

Venue slugs are not interchangeable. For example, a Kalshi soccer slug such as `kalshi-soccer-ieloak-2026-09-20` and a Polymarket slug such as `uslc-iel-oak-2026-09-20` can refer to the same fixture even though their text differs. Compare the published fixture, participants and start time, then inspect the contract mapping and settlement terms. The fixture key is a candidate grouping aid; it is not permission to join books.

### Fixture and market endpoints

```
GET /v3/sports/markets
GET /v3/sports/markets/{condition_id}
GET /v3/sports/games/{game_slug}
GET /v3/sports/games/{game_slug}/markets
```

The fixture-market endpoint is paginated. Follow its cursor to retrieve the complete collection. There is no `cross_venue_key` listing filter; collect candidate groups in your own store. Fixture dates may use the local game date, while `game_start` is a UTC instant. Do not reconstruct one from the other. Doubleheaders and future seasons require their full published identities.

The joined game document embeds a preview of at most 1,000 markets. Inspect `markets_returned` and `markets_truncated`; use `markets_url` and follow that endpoint's cursor from the beginning for the complete collection. The preview's volume ordering is not a pagination checkpoint. Failed game-market, result, odds or injury reads return HTTP 503 with `CATALOGUE_UNAVAILABLE`, rather than a cached successful response with empty substitutes. Keep previously stored data and retry. A successful empty array still means the corresponding lookup returned no records.

Market lookups first check for an exact `condition_id`, then accept native token IDs as a fallback. Historical catalogue versions are used only to discover possible parent records; current ownership is verified before returning a parent. Multiple current owners in that fallback are reported as an unavailable lookup, not permission to choose one arbitrarily. Prefer `condition_id` for upserts and retain native token IDs for book requests.

## Incremental updates without full reloads

Sports listings support `updated_since` as epoch milliseconds, ISO-8601 or a date. `updated_at` and `updated_ms` are catalogue publication times, not settlement times. Identity, kickoff and contract-mapping corrections publish market updates. Evidence renewals are also published so consumers can refresh validity timestamps.

Evidence expiry is a local deadline: stop using confirmed keys at `evidence_valid_until_ms` even before the next publication. Expiry preserves participant and candidate identities. Persisted evidence renewals and revocations publish deltas. The September 19 correction-publication contract supersedes earlier support guidance about fixture changes bypassing `updated_at`. New fields must not be assumed to have been backfilled unless their release explicitly republishes existing records.

1. Save the poll-start time and your previous checkpoint.
2. Keep `updated_since` and all filters fixed while following every `next_cursor`.
3. Upsert complete market records by `condition_id`, including nulls that revoke old mappings.
4. Include resolved markets. Do not use `resolved=false` for steady-state correction polling.
5. After every page succeeds, advance the checkpoint from the poll-start time with an overlap that covers your delivery delay and clock skew. Do not advance to the largest update timestamp observed during a changing traversal.
6. Retry a failed traversal from the previous checkpoint. Reprocessing the overlap must be idempotent.

`updated_since` updates market metadata. It does not deliver corrected historical book rows or modify a client's warehouse. Retrieve affected historical windows again when historical captures need refreshing. Mapping keys can be revoked or replaced; retain the market's stable identifier as the upsert key.

## Pagination and page caps

Catalogue and book-history caps depend on the plan and route. The current standard caps are:

| Plan | Catalogue rows | Book-history rows |
|---|---:|---:|
| Explorer | 200 | 500 |
| Quant | 400 | 750 |
| Research | 500 | 1,000 |
| Desk | 5,000 | 5,000 |

These are not universal caps for trades, ladders, reference datasets or every other route. Endpoint-specific caps still apply. Read the returned pagination envelope and the account information from `/v3/whoami`.

Cursors are opaque. Reuse them only on the same endpoint and with the same identity, time range, resolution and filters. Do not construct a cursor from the last timestamp. Several distinct captures can share one exchange timestamp; current raw snapshot cursors preserve those records across page boundaries. Old timestamp cursors remain accepted, but new integrations must use the cursor returned by the service.

Continue while `has_more` is true using `next_cursor`, including after an empty page. If the service repeats a cursor or supplies no cursor while claiming more, stop and retry from the saved checkpoint rather than silently skipping data.

`pagination.count` describes the returned page. `include_count=true` requests a total for the filtered result; it is not proof of venue-wide capture completeness. Market-level crypto snapshots coalesce captures at the same exchange timestamp and count those timestamp groups. The raw Polymarket token endpoint can return multiple captures at that timestamp.

## Captures, sampled books and ticks

An interval is a sampling grid, not a guarantee that the source was recorded at that frequency. `interval=1s` does not create one genuine capture every second. Without forward fill, a bucket with no eligible capture can be absent. With `fill=ffill`, the last known book is carried forward where a seed exists; inspect `filled` and `as_of_ts` to distinguish carried observations from captures.

Sports history returns newest first. Crypto and venue snapshot routes return oldest first. Preserve the returned cursor and use timestamps when combining streams.

Use the corresponding ticks routes when individual captured updates are needed. A sampled book endpoint and an event stream have different semantics. Missing observations cannot be reconstructed from settlement metadata.

Sparse results can reflect capture cadence, source availability or missing history; row count alone does not establish why. A completed page traversal or `resolution.complete` is not a guarantee that every venue event was captured. Compare timestamps and coverage information for the relevant window.

## Prices, volume and successful-but-partial responses

Inspect `meta.partial` and accompanying metadata before storing a response as complete. Price enrichment can be temporarily unavailable even when the request returns HTTP 200. A null price must not be converted to zero. Retry transiently partial responses while preserving your checkpoint.

Sports `volume` is USD on Polymarket and contracts on Kalshi. Use `volume_unit`; do not sum unlike units. `volume_usd` provides the available USD measure, with `volume_usd_complete` and `volume_usd_from` identifying its coverage. An incomplete total is not a lifetime total, and zero recorded eligible volume is not proof that no trading occurred.

## Concurrency and backpressure

General request-rate limits, history concurrency, delivery limits and live subscriptions are separate controls. Account profiles can override defaults. Read the effective history policy from `/v3/whoami`; do not infer it from advertised requests per second or another customer's configuration.

For `429`, inspect the error code, scope and `Retry-After`. `HISTORY_CONCURRENCY_LIMIT` means your account has occupied its lanes; reduce simultaneous work. `HISTORY_POOL_BUSY` means the shared serving pool is busy; retry with bounded backoff. A five-minute repair timer is a scheduling cadence, not a promise that every backfill completes in five minutes.

## Migration checklist

- Replace cross-venue joins based on `game_slug` or outcome-array position.
- Keep candidate grouping separate from confirmed contract and outcome keys.
- Handle `unmatched`, `needs_review`, null values and mapping revocations explicitly.
- Poll corrections for resolved as well as open markets.
- Stop treating one-second grids, HTTP 200 or total counts as completeness certificates.
- Use per-route identifiers, opaque cursors, effective account limits and retry metadata.
- Re-fetch previously incomplete historical windows where necessary; metadata polling alone cannot repair them.

Kalshi contract evidence includes the governing PDF URL and SHA-256 under each source contract’s `series` descriptor when available. Current series terms require a separate applicability review for historical contracts. Missing documents or incomplete rule review prevent confirmed equivalence. See /mapping-contract.md for the eleven review dimensions and refresh limitations.

Candidate keys are mutable grouping keys, not market primary keys. Use condition_id for upserts and retain row versions for historical joins. Inspect candidate_group for native venue multiplicity; no authoritative duplicate selector is implied. Player metadata and Pinnacle lookup failures return 503 instead of successful empty defaults. See the detailed mapping contract for missing-value semantics.

Sports history existence and storage-bound lookup failures return 503 with HISTORY_UNAVAILABLE. A failed odds-stream starting-checkpoint lookup returns ODDS_DATA_UNAVAILABLE, and unavailable webhook delivery health returns WEBHOOK_HEALTH_UNAVAILABLE rather than zero delivery counts. Preserve the last successful data and checkpoint when retrying these responses. Once an odds SSE stream is open, an error event is a failed poll, not an empty successful update.
