Getting started
This guide takes you from zero to your first API call in about five minutes. The SharpRelay API delivers Pinnacle-sourced pre-game and live odds, opening & closing lines, results, limits, and fair (margin-free) odds over one REST API, authenticated with a single API key.
The market data is sourced from Pinnacle and delivered through SharpRelay's independent API, authentication, metering, and reliability layer. SharpRelay is not affiliated with, endorsed by, or sponsored by Pinnacle.
Version 1 publishes 12 authenticated, read-only data routes.
For props, discover markets on an event and request latest or available
historical prices for one prop_id at a time. Discover outrights
separately and use each returned special_id as the
prop_id for its prices. Event-wide bulk prop prices and
prop-specific opening, closing, results, and CLV operations are not exposed in
the v1 private beta.
Activate your verified invitation
Private-beta access is issued only to a manually reviewed business. Open the single-use link supplied by the operator, review the exact displayed policies, confirm that you are authorized to bind the invited business, and complete the same-origin security challenge. The invitation arrives only in the URL fragment; the first-party page removes it from the address before activation and never sends it in a request target. Do not copy it into a query string, log, support ticket, or command line.
Successful activation opens the private one-time claim page. Save the API key
immediately; neither the invitation nor the claim page starts a paid
subscription, and beta access never converts to paid access automatically. The
primary key has data:read and account:manage; create
narrower data:read-only keys for deployed pollers.
A separate message asks you to verify the account mailbox for future lost-key recovery. Verification grants recovery authority but never reveals or changes an API key. Normal API use is available before it is complete.
Store your API key safely
Your key looks like sr_live_a1b2c3…. Keep it in a secrets
manager or environment variable — never in source control, client-side code,
or mobile apps. Full guidance in Authentication.
Store the first successful response immediately. The exact claim handoff can recover an interrupted delivery for only 10 minutes. If you later lose one key, create or rotate a replacement; if you lose every management key, the secure recovery page can send a link only to the exact account mailbox you verified beforehand. Check the dashboard's Recovery email status while a management key still works.
Make your first call
Every request passes the key as a Bearer token:
$ curl -H "Authorization: Bearer sr_live_…" \
https://api.worksinprod.dev/v1/catalog/sports
Response — a JSON array, plus exact metering and integrity headers:
HTTP/1.1 200 OK
Content-Type: application/json
X-Credits-Limit: 100000
X-Credits-Remaining: 99999
X-Credits-Period-Limit: 100000
X-Credits-Period-Remaining: 99999
X-Credits-Period-Reset: 2026-08-01T00:00:00Z
X-Credits-Daily-Limit: 10000
X-Credits-Daily-Remaining: 9999
X-Credits-Daily-Reset: 2026-07-23T00:00:00Z
X-Credits-Cost: 1
X-Result-Rows: 2
X-Result-Truncated: false
X-Next-Cursor: (present only when another live-event page exists)
X-Cache: MISS
X-Request-Id: req_9f12…
X-SharpRelay-Origin: https://api.worksinprod.dev
[{"sport_id": 29, "name": "Soccer"}, {"sport_id": 1, "name": "Tennis"}]
Ordinary routes use one credit per started block of up to 20 returned
rows. Verified live=1 discovery can cost more because its charge
is the greater of returned-row credits and bounded discovery/offer-check work;
narrow filters and limit control that work. The
X-Credits-* headers tell you where you stand on every metered
response — see
Quotas & metering.
Your first script
Discover ordinary pre-match events with /v1/events; without a
kickoff window it returns only events whose start remains strictly after the
request boundary and is still in the future when the response is delivered.
Supplying an explicit
starts_from/starts_to window narrows that lifecycle
window. A present or future starts_from remains locally enforced as
a future lower bound; only a genuinely past starts_from opts into
historical discovery. For future discovery, starts_to is also locally
enforced as an inclusive timestamp or complete UTC-day upper bound.
An exact event_id or parent_id lookup is likewise
lifecycle-specific. Use live=1 separately for currently verified
in-play offers.
The odds call without full_history or since is the
lowest-latency latest-snapshot profile. For event odds, this means every market
row at the greatest positive line_id for each event and period;
older off-board line IDs are excluded. This minimal poller switches to an
explicit moving since cursor only after its first snapshot:
# Python (pip install requests)
import time, requests
API, KEY = "https://api.worksinprod.dev", "sr_live_…"
H = {"Authorization": f"Bearer {KEY}"}
event_id = 123456
cursor = None
while True:
params = {}
if cursor:
params["since"] = cursor
response = requests.get(f"{API}/v1/events/{event_id}/odds", headers=H, params=params)
response.raise_for_status()
rows = response.json()
if response.headers.get("X-Result-Truncated") == "true":
raise RuntimeError("history was truncated; do not advance the cursor")
if rows:
cursor = max(r["timestamp"] for r in rows)
print(f"{len(rows)} new odds rows")
time.sleep(5)
// Node.js 18+
const API = "https://api.worksinprod.dev", KEY = "sr_live_…";
const eventId = 123456;
let cursor = null;
for (;;) {
const url = new URL(`${API}/v1/events/${eventId}/odds`);
if (cursor) url.searchParams.set("since", cursor);
const response = await fetch(url, {
headers: { Authorization: `Bearer ${KEY}` }
});
if (!response.ok) throw new Error(`request failed: ${response.status}`);
const rows = await response.json();
if (response.headers.get("X-Result-Truncated") === "true") {
throw new Error("history was truncated; do not advance the cursor");
}
if (rows.length) {
cursor = rows.reduce((a, r) => a > r.timestamp ? a : r.timestamp, rows[0].timestamp);
console.log(`${rows.length} new odds rows`);
}
await new Promise(r => setTimeout(r, 5000));
}
Next steps
| Topic | What you'll learn |
|---|---|
| Quotas & metering | Credit model, allowance headers, rate limits, monitoring usage |
| Polling & performance | Cheap incremental polling, caching, freshness trade-offs |
| Workflows | End-to-end recipes: fixtures → odds → closing, live loops, backfills |
| Errors & retries | Error envelope, codes, retry strategy that stays within limits |
| API reference | Every endpoint, parameter and response |
| Dashboard | Live quota, usage charts, key management |