SharpRelayPinnacle data
HomeAPI referenceDashboard

Polling & performance

Odds data is time-series data: the difference between a naive client and an efficient one can be 100× in credit use. This guide covers the patterns that keep you fast and cheap.

Choose the correct odds profile

Ordinary /v1/events discovery is future pre-match by default; without an explicit window or identifier, returned starts remain strictly after the request boundary and in the future at final validation. A present or future starts_from remains locally enforced as a future lower bound; only a genuinely past value opts into historical discovery. Pair it with starts_to to bound the intended window; the upper timestamp is inclusive and a date-only value includes that complete UTC day. Both future bounds are enforced locally after source validation. Exact event and parent lookups are lifecycle-specific. Use live=1 separately for the currently verified in-play set. Live discovery is not response-cached: every request reads the complete bounded candidate boundary for the selected filters, then performs bounded per-page verification. A returned event must have live_status=1; its latest odds must be bound to that event and contain an open line whose cutoff remains strictly future when expiry is rechecked at final validation. There is no arbitrary update-age or kickoff-age floor for a legitimately long-running open event. Because this verified scan performs work beyond returned rows, its variable charge is the greater of returned-row credits and bounded discovery/offer-check work through the same 20-unit blocks. Narrow filters and limit, and treat X-Credits-Cost as authoritative. Broad discovery reads at most 1,000 fixture candidates per page, then verifies at most max(4, 2 × limit) candidates, capped at 512; an exact event_id lookup verifies only that event. A page can therefore return fewer offers than limit.

For the lowest-latency current view, call an odds endpoint without full_history; for event odds, also omit since. Store the latest snapshot and compare it locally. Event odds select the greatest positive line_id for each event and period, retaining every primary and alternate market row at that winning snapshot while excluding older off-board line IDs. Prop odds select one greatest-line_id row per special_id + contestant_line_id; handicap is a value on that row, not a separate latest identity. Use an event-odds since timestamp only when your application needs available matching timeline updates after that cursor: it selects historical retrieval and can be materially larger and slower than the latest-snapshot path. Use since by itself (or with full_history=1); since=...&full_history=0 is rejected. Always inspect X-Result-Truncated and never assume a historical response is complete.

cursor = None
while True:
    params = {}
    if cursor:
        params["since"] = cursor
    response = GET_RESPONSE(f"/v1/events/{EVENT}/odds", params)
    rows = response.json()
    if response.headers.get("X-Result-Truncated") == "true":
        raise RuntimeError("history was truncated; do not advance the cursor")
    if rows:
        process(rows)
        cursor = max(r["timestamp"] for r in rows)   # advance only after success
    sleep(5)
Anti-pattern

Re-using a fixed since value re-fetches the same window on every call and bills you for every row, every time. The cursor must move.

Caching & freshness

Reference, discovery, and selected lifecycle data can be served from SharpRelay's disposable response cache, which removes the source round trip and minimizes gateway overhead. End-to-end latency still depends on client location and network conditions. Cached responses use the same credit metering (they still bill normally). The X-Cache header tells you which path a response took:

X-CacheMeaning
MISSThe disposable cache was available but had no usable entry, so the response was fetched from the source. A MISS does not guarantee the body was stored: lifecycle-empty or oversized responses and best-effort write failures can skip storage.
HITServed from the disposable cache, avoiding the source round trip
(absent)The route is not cache-eligible, caching is disabled, or the disposable cache was unavailable. The response has no cache-path freshness guarantee.
Endpoint classExamplesMax cache age
Reference data/v1/catalog/sports, /v1/catalog/leagues, /v1/catalog/periods~1 hour
Opening snapshot/v1/events/{event_id}/lines/openingUp to ~24 hours after a non-empty response
Closing and settlement/v1/events/{event_id}/lines/closing, /v1/events/{event_id}/results~30 seconds; empty responses are not cached
Verified live discovery/v1/events?live=1Not response-cached; every returned event has live_status=1, event-bound latest odds, and an open line whose cutoff remains strictly future when expiry is rechecked at final validation
Ordinary event and prop discovery/v1/events, /v1/events/{event_id}/props~30 seconds
Outright discovery/v1/outrights~5 minutes
Odds timelines/v1/events/{event_id}/odds, /v1/props/{prop_id}/odds, /v1/analytics/clvnot response-cached

Cache age starts when SharpRelay stores a response; it is not a promise about when the underlying data was first published. Empty opening, closing, and result responses are never cached because those lifecycle rows can appear later. Closing lines and results are also mutable: corrections and re-settlements can change a non-empty response, so refresh them whenever revisions matter.

Practical upshot: call reference endpoints at startup and refresh them on a slow timer — never per event. Ordinary future-card discovery can be cached for roughly 30 seconds. Verified live=1 discovery always goes to the source verification path and therefore has no X-Cache header. For other routes, inspect X-Cache instead of inferring the path from latency.

Concurrency

Complete current-live discovery

A large in-play slate can require several verified event pages. Start with live=1 and a bounded limit, then pass each opaque X-Next-Cursor back as the cursor query parameter while repeating the original sport, league, and page-size filters. Continue after an empty page: it can mean the scanned candidates were stale or suspended, while a later page can still contain an open offer. The scan is complete only when the cursor header is absent and X-Result-Truncated is false.

This mode is intentionally different from ordinary future pre-match discovery: a candidate is returned only after SharpRelay verifies an event-bound latest open line whose cutoff remains strictly future when expiry is rechecked at final validation. There is no separate odds-update-age rule. A past kickoff by itself does not make a row historical while the event is still actively offered in play.

Each live page is freshly source-verified and is not served from the response cache. Repeating a page is therefore a new current observation, not a replay of a previous live response.

Snapshot boundary

A cursor is filter-bound, preserves the first page's kickoff window, and expires after five minutes. Restart from page one for a new live snapshot. If a response is truncated but has no next cursor, narrow the kickoff window or league; never silently treat that response as complete.

Transport efficiency