Workflows
End-to-end recipes for common integrations across the 12 published, read-only v1 private-beta data routes. Every example uses only the endpoints in the API reference.
1 · Pre-match pipeline: sports → fixtures → odds → closing
The canonical flow for pre-game analysis:
# 1. Discover the sport and league IDs (cache these — they rarely change) $ curl -H "Authorization: Bearer sr_live_…" "https://api.worksinprod.dev/v1/catalog/sports" $ curl -H "Authorization: Bearer sr_live_…" "https://api.worksinprod.dev/v1/catalog/leagues?sport_id=29" # 2. List upcoming events for the league $ curl -H "Authorization: Bearer sr_live_…" \ "https://api.worksinprod.dev/v1/events?sport_id=29&league_id=123" # 3. Pull current odds for an event $ curl -H "Authorization: Bearer sr_live_…" \ "https://api.worksinprod.dev/v1/events/987654/odds" # 4. After kickoff/settlement: closing line, fair odds and score $ curl -H "Authorization: Bearer sr_live_…" \ "https://api.worksinprod.dev/v1/events/987654/lines/closing"
The ordinary event collection is future pre-match by default: without an
explicit window or identifier, starts remain strictly after the request boundary
and in the future at final validation. The example therefore does not need a
date that will become stale. A present or future starts_from remains
locally enforced as the future lower bound, while starts_to remains
the inclusive timestamp or complete UTC-day upper bound. Only a genuinely past
starts_from intentionally selects historical fixtures. Exact
event_id or parent_id lookups are lifecycle-specific.
Steps 1–2 are cacheable — fetch them at startup and on a slow refresh. Step 3
is current price data; omit full_history and since for the
lowest-latency latest snapshot. That response selects the greatest positive
line_id per event and period and returns every primary and alternate
market row present at that winning snapshot; older off-board line IDs are
excluded. Closing lines and results can be corrected or
re-settled; poll them until your business-defined stability point, and refresh
them later whenever revisions matter rather than treating one response as
immutable.
2 · Live-odds tracking loop
Track in-play movement for a large event set with a bounded worker pool.
Discover the current set with /v1/events?live=1. When
starts_from is omitted, discovery applies no minimum kickoff; use an
explicit UTC value only to narrow the candidate window. Omit since
for a complete snapshot because it filters fixture metadata, not price changes.
Every live request reads the complete bounded candidate boundary for the
selected filters, orders it deterministically, and 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 explicitly open line whose
cutoff remains strictly in the future when expiry is rechecked at final
validation. Live pages are not response-cached, and no arbitrary odds-update-age
or kickoff-age rule is imposed. The
variable charge is the greater of returned-row credits and bounded verified
discovery/offer-check work through the same 20-unit blocks, so narrow the event
filters and limit and inspect X-Credits-Cost. 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 contain fewer
offers than limit.
The default odds request returns the winning event-and-period line-ID snapshot;
compare its stable market/line/alternate-line identities and timestamps with
your local state. This is the preferred
low-latency path. Use since only when you need available matching
timeline updates after a cursor, because it selects the heavier historical query
profile. Inspect X-Result-Truncated and never assume the returned
history is complete.
// Node.js 18+ — discover every verified live event before price fan-out
const API = "https://api.worksinprod.dev";
const KEY = process.env.SHARPRELAY_API_KEY;
if (!KEY) throw new Error("SHARPRELAY_API_KEY is required");
const headers = {
Authorization: `Bearer ${KEY}`,
Accept: "application/json"
};
async function GET(path) {
const response = await fetch(`${API}${path}`, { headers });
const body = await response.json();
if (!response.ok) {
throw new Error(`${body?.error?.code || response.status} (${body?.error?.request_id || "no request id"})`);
}
return body;
}
async function boundedMap(values, concurrency, mapper) {
const output = new Array(values.length);
let next = 0;
async function worker() {
while (next < values.length) {
const index = next++;
output[index] = await mapper(values[index]);
}
}
await Promise.all(Array.from(
{ length: Math.min(concurrency, values.length) },
() => worker()
));
return output;
}
const filters = { sport_id: "29", live: "1", limit: "50" };
const events = new Map();
let cursor = "";
do {
const query = new URLSearchParams(filters);
if (cursor) query.set("cursor", cursor);
const response = await fetch(`${API}/v1/events?${query}`, { headers });
if (!response.ok) throw new Error(`event discovery failed: ${response.status}`);
for (const event of await response.json()) events.set(event.event_id, event);
cursor = response.headers.get("X-Next-Cursor") || "";
if (!cursor && response.headers.get("X-Result-Truncated") === "true") {
throw new Error("incomplete discovery: narrow the kickoff window or league");
}
} while (cursor);
const completeEventSet = [...events.values()];
const output = await boundedMap(completeEventSet, 8, async event => ({
event_id: event.event_id,
match: { home: event.runner_home, away: event.runner_away },
starts: event.starts,
odds: await GET(`/v1/events/${event.event_id}/odds?full_history=0&main_lines_only=0`)
}));
console.log(JSON.stringify({
generated_at: new Date().toISOString(),
live_event_count: output.length,
events: output
}, null, 2));
X-Next-Cursor is opaque, bound to the original filters and
kickoff snapshot, and valid for five minutes. Follow it even when an
intermediate body is empty. The block above is directly runnable with Node.js
18 or newer after setting SHARPRELAY_API_KEY. A retry-capable
reference client is also included in the project as
examples/live-soccer-odds.mjs.
Start with eight workers, reuse connections, measure a complete batch, and increase gradually only while staying below the account rate limit. Do not start hundreds of new TCP connections or retry a whole batch in lockstep. Batch time is not a fixed SLA: event complexity, network conditions, and live data availability all affect it.
3 · Historical backfill
Building a local database of available historical timelines:
# An explicit past kickoff window opts into historical event discovery $ curl -H "Authorization: Bearer sr_live_…" \ "https://api.worksinprod.dev/v1/events?sport_id=29&starts_from=START_UTC&starts_to=END_UTC"
- Enumerate events with
/v1/eventsover date windows (starts_from/starts_to), not with giant single calls. - Request each event's available matching timeline once with
/v1/events/{event_id}/odds?full_history=1. Historical depth varies with source availability, and deep histories can contain many rows — budget credits usingmax(1, ceil(rows / 20)). InspectX-Result-Truncatedand never assume the response is a complete event history. - Persist results and avoid repeating an identical backfill after you have
recorded its truncation state. After the initial fill, use latest-snapshot
polling for the fast path and request a moving
sincecursor only when available intermediate updates after that cursor are required. Usesinceby itself (or withfull_history=1); the contradictory combinationsince=...&full_history=0is rejected.
Backfills are the one workflow that can burn real quota fast. Estimate rows first (latest-only call tells you the market count), then run the backfill in batches with a credit budget per run.
4 · Closing line value (CLV) analysis
Measure whether your taken prices beat the closing fair line:
# Closing fair odds for the event $ curl -H "Authorization: Bearer sr_live_…" \ "https://api.worksinprod.dev/v1/events/987654/lines/closing"
Compare your recorded taken price against the closing todds*
(fair) fields: a consistently better taken price than the close indicates
positive expected value. /v1/analytics/clv provides pre-computed CLV
metrics where available.
5 · Props and outrights
The private-beta prop surface is deliberately explicit and one market at a time:
# Discover props attached to one event $ curl -H "Authorization: Bearer sr_live_…" \ "https://api.worksinprod.dev/v1/events/987654/props" # Use a returned special_id as prop_id for the latest contestant prices $ curl -H "Authorization: Bearer sr_live_…" \ "https://api.worksinprod.dev/v1/props/246810/odds" # Request the available history for that same prop $ curl -H "Authorization: Bearer sr_live_…" \ "https://api.worksinprod.dev/v1/props/246810/odds?full_history=1" # Discover outright markets, then price each returned special_id as above $ curl -H "Authorization: Bearer sr_live_…" \ "https://api.worksinprod.dev/v1/outrights?sport_id=29"
Event-wide bulk prop prices and prop-specific opening, closing, results, and CLV operations are not exposed in the v1 private beta. Do not infer hidden aliases: integrate only against the published reference routes.
Field reference
| Field | Meaning |
|---|---|
odds1 / odds0 / odds2 | Decimal odds: home / draw / away (over / draw / under for totals) |
todds1 / todds0 / todds2 | True (margin-free) fair odds — the de-vigged reference price |
line | Spread/total/handicap value; null for moneyline |
period | 0 = full match, 1 = first half/period/set, … (see /v1/catalog/periods per sport) |
market | moneyline, spread, totals, home_totals, away_totals |
live_status | Numeric event-state signal supplied with the fixture. A returned live=1 event has live_status=1, but the field alone is not proof of availability: SharpRelay also binds a latest odds snapshot to that event and verifies an explicitly open line whose cutoff is strictly future when expiry is rechecked at final validation. |
line_id | Snapshot identifier. Event latest mode keeps all market rows at the greatest positive value per event and period; prop latest mode keeps the greatest positive value per special and contestant line. |
timestamp | Snapshot time, UTC |