SharpRelayPinnacle data
HomeAPI referenceDashboard

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"
Cost control

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

FieldMeaning
odds1 / odds0 / odds2Decimal odds: home / draw / away (over / draw / under for totals)
todds1 / todds0 / todds2True (margin-free) fair odds — the de-vigged reference price
lineSpread/total/handicap value; null for moneyline
period0 = full match, 1 = first half/period/set, … (see /v1/catalog/periods per sport)
marketmoneyline, spread, totals, home_totals, away_totals
live_statusNumeric 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_idSnapshot 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.
timestampSnapshot time, UTC