Pick API
Submit your system's picks before the event. We stamp the market price the moment the batch arrives, freeze each pick, grade it after the event, and build the verified record on your algorithm's page. Free, private by default.
Setup
- Sign in and open My algorithms. Choose Connect your system: a name, the league, a paragraph, and whether picks are revealed before start. Note the algorithm id on its page.
- In Settings, mint an API key. The secret is shown once and looks like
hk_live_…. Up to five active keys; revoke any time. - Send picks to
POST https://hindsight-production-e3c9.up.railway.app/pickswith the key as a bearer token. That is the whole integration. A missing or malformed key is a401API key required; a well-formed key we do not recognise is a401invalid or revoked API key.
Submit picks
One request, up to 200 picks. Each item names your algorithm (id or name), the league, the event, the market, and the selection. Nothing about price or time is in the body: we stamp both at receipt. Every item gets its own result, keyed by index, and a rejected item never sinks the rest. The one exception is shape: an item with neither event form, with both, or with a field out of range fails the whole request as a 422 before anything is stamped.
curl -X POST https://hindsight-production-e3c9.up.railway.app/picks \
-H "Authorization: Bearer hk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"picks": [
{
"algorithm": 123,
"sport": "MLB",
"game_date": "2026-09-08",
"home": "BOS",
"away": "NYY",
"market": "moneyline",
"selection": "home"
},
{
"algorithm": 123,
"sport": "MLB",
"event": "Yankees at Red Sox 9/8",
"market": "total",
"selection": "over",
"line": 8.5
},
{
"algorithm": "My CS2 model",
"sport": "CS2",
"event": "NaVi vs Vitality, IEM Cologne",
"market": "moneyline",
"selection": "Vitality"
}
]
}'Identifying the event
Either the structured fields or free text, never both.
| field | meaning |
|---|---|
| sport | A registered league code: MLB, NFL, NHL and NBA (our schedule and results), or CS2, DOTA2, VALORANT and NCAAF (resolved and settled from public sources). The same codes the league picker shows when you connect a system. |
| game_date, home, away, game_number | game_date is the game's date in Eastern time as YYYY-MM-DD; home and away are the codes the schedule returns (home_code / away_code from GET /events); game_number defaults to 1 (the second game of a doubleheader is 2). |
| event | Free text: team names or codes, an optional date, an optional tournament. Resolved against our schedule for tracked leagues and against public sources otherwise; an ambiguous mention is rejected with the candidates. |
Markets and selections
The line is always the home side's number. A side selection may be a team mention instead of home/away.
| market | selection | line |
|---|---|---|
| moneyline | home · away (or a team name) | none |
| spread | home · away | home side's number, e.g. -1.5 for a home favourite |
| total | over · under | the total, e.g. 8.5 |
| team_total | home_over · home_under · away_over · away_under | that team's total |
| f5_moneyline | home · away | none (first five innings, MLB) |
| f5_total | over · under | the first-five total |
The receipt
For the batch above. An accepted pick carries its id, the receipt time, the stamped price for your side and the other side (or priced: false when no capture covers the market), and the full event it resolved to. Every result carries every field; the ones that do not apply to its status are null, so one parser handles both.
{
"results": [
{
"index": 0,
"status": "accepted",
"pick_id": 9812,
"submitted_at": "2026-09-08T14:02:11.418Z",
"priced": true,
"stamped_price": -135,
"stamped_opposite_price": 115,
"stamped_provider": "DraftKings",
"stamped_at": "2026-09-08T13:00:04.902Z",
"event": {
"id": 4410,
"event_key": "MLB:2026-09-08:NYY@BOS:1",
"sport": "MLB",
"home_name": "Boston Red Sox",
"away_name": "New York Yankees",
"home_code": "BOS",
"away_code": "NYY",
"scheduled_start_at": "2026-09-08T23:10:00Z",
"tier": "deterministic"
},
"reason": null,
"detail": null,
"candidates": [],
"existing_pick_id": null
},
{
"index": 1,
"status": "accepted",
"pick_id": 9813,
"submitted_at": "2026-09-08T14:02:11.418Z",
"priced": false,
"stamped_price": null,
"stamped_opposite_price": null,
"stamped_provider": null,
"stamped_at": null,
"event": {
"id": 4410,
"event_key": "MLB:2026-09-08:NYY@BOS:1",
"sport": "MLB",
"home_name": "Boston Red Sox",
"away_name": "New York Yankees",
"home_code": "BOS",
"away_code": "NYY",
"scheduled_start_at": "2026-09-08T23:10:00Z",
"tier": "deterministic"
},
"reason": null,
"detail": null,
"candidates": [],
"existing_pick_id": null
},
{
"index": 2,
"status": "rejected",
"pick_id": null,
"submitted_at": null,
"priced": null,
"stamped_price": null,
"stamped_opposite_price": null,
"stamped_provider": null,
"stamped_at": null,
"event": null,
"reason": "ambiguous",
"detail": "2 CS2 events match ('NaVi', 'Vitality'); add the date",
"candidates": [
{
"id": null,
"event_key": "CS2:2026-09-09:Natus_Vincere@Team_Vitality:1",
"sport": "CS2",
"home_name": "Team Vitality",
"away_name": "Natus Vincere",
"home_code": "VIT",
"away_code": "NAVI",
"scheduled_start_at": "2026-09-09T13:00:00Z",
"tier": "evidence"
},
{
"id": null,
"event_key": "CS2:2026-09-11:Natus_Vincere@Team_Vitality:1",
"sport": "CS2",
"home_name": "Team Vitality",
"away_name": "Natus Vincere",
"home_code": "VIT",
"away_code": "NAVI",
"scheduled_start_at": "2026-09-11T16:30:00Z",
"tier": "evidence"
}
],
"existing_pick_id": null
}
]
}| reason | what it means |
|---|---|
| invalid | Unknown market, a selection that does not fit it, a missing line, or a league that is not the algorithm's own. |
| unknown_algorithm | No API-fed algorithm of yours by that id or name (or the name is ambiguous; use the id). |
| unmapped | An unknown league code, or no scheduled event matches; for a tracked league only our schedule counts. |
| ambiguous | More than one event matches; candidates are listed. Add the date or game number. |
| already_started | The event's scheduled start has passed on our clock. |
| duplicate | The same (algorithm, event, market, selection, line) already exists; its id is returned. |
| rate_limited | Past the daily cap for this key; Retry-After says when it resets. |
Withdraw before start
A pick can be voided by its owner before the event starts, never after and never once settled. Within 15 minutes of receipt the void is a correction: it leaves the record and readers never see it. After that it is a withdrawal: the pick stays in the history with its time, counts nowhere as a result, and the record shows how often the system withdraws. A change of mind is a withdrawal plus a new pick. Nothing is ever edited or deleted. After a correction the same pick may be submitted again as a fresh pick, which is what the window is for; after a withdrawal it is a duplicate. The call is a 404 for a pick that is not yours, and a 409 once the event has started or the pick is settled.
curl -X DELETE https://hindsight-production-e3c9.up.railway.app/picks/9812 \ -H "Authorization: Bearer hk_live_YOUR_KEY"
Read back
Your own picks with settlement state, for reconciliation, and the resolvable schedule so you can look up a doubleheader before submitting. Your own read-back always shows your stamped prices, sealed or not; the reveal setting only governs what other readers see. Events exist as far ahead as the league's schedule is loaded; a date with none returns an empty list.
# your own picks, with settlement state (from/to bound the event's ET date; # limit defaults to 500, max 2000) curl "https://hindsight-production-e3c9.up.railway.app/picks?algorithm=123&from=2026-09-01&to=2026-09-08" \ -H "Authorization: Bearer hk_live_YOUR_KEY" # the resolvable schedule for a date (no key needed; date defaults to today ET) curl "https://hindsight-production-e3c9.up.railway.app/events?sport=MLB&date=2026-09-08" # one event, and the settlement evidence behind it (no key needed) curl "https://hindsight-production-e3c9.up.railway.app/events/4410" curl "https://hindsight-production-e3c9.up.railway.app/events/4410/evidence" # what a reader sees of an algorithm's picks (no key needed; the reveal rule applies) curl "https://hindsight-production-e3c9.up.railway.app/algorithms/123/picks?date=2026-09-08"
A pick on the record
One item of GET https://hindsight-production-e3c9.up.railway.app/picks after settlement. Before settlement the outcome, the close, and settlement_tier are null. A voided pick has outcome: "void" and a void_reason: yours (corrected, withdrawn), the worker's when the event is postponed or dropped from the schedule (postponed, dropped), or a reviewer's (review). review_pending is true while a tier-2 settlement waits for a person.
{
"id": 9812,
"algorithm_id": 123,
"event": {
"id": 4410,
"event_key": "MLB:2026-09-08:NYY@BOS:1",
"sport": "MLB",
"home_name": "Boston Red Sox",
"away_name": "New York Yankees",
"home_code": "BOS",
"away_code": "NYY",
"scheduled_start_at": "2026-09-08T23:10:00Z",
"tier": "deterministic"
},
"market": "moneyline",
"selection": "home",
"line": null,
"submitted_at": "2026-09-08T14:02:11.418Z",
"priced": true,
"stamped_price": -135,
"stamped_opposite_price": 115,
"stamped_provider": "DraftKings",
"stamped_at": "2026-09-08T13:00:04.902Z",
"close_price": -142,
"close_opposite_price": 120,
"close_provider": "DraftKings",
"outcome": "win",
"settled_at": "2026-09-09T02:41:07.115Z",
"settlement_tier": "deterministic",
"void_reason": null,
"withdrawn_at": null,
"event_status": "final",
"review_pending": false
}What happens after
- Stamped at receipt. The price is the newest one our own capture holds for that market at the moment the batch arrives, with the book (
stamped_provider) and the capture time on the pick. We never take a price from the body. A pick with no capture at receipt stays unpriced for good: it counts for accuracy, never for units or closing-line value. A league without a price feed carries an accuracy-only record and says so. - Sealed until start unless you turn on reveal before start. Followers of a sealed system get “posted N picks”; the picks appear as each event starts.
- Closed and settled. The last price our capture holds before start is the close, recorded for closing-line value. Tracked leagues (
tier: "deterministic") settle from our own results; other leagues (tier: "evidence") settle when two independent public sources agree, with the evidence attached to the event. A disagreement waits for a person and the pick shows “pending review”. A postponed game, or one dropped from the schedule, voids every open pick on it; a void counts nowhere. - On the record. Accuracy, flat-stake units, dog/chalk splits and CLV, with sample-size bands, the tier mark and priced-vs-unpriced counts, on your algorithm's page and in the directory.
- Read for one purpose. Before start, a pick is read only to stamp, grade, and settle it. It is never sold, shared, aggregated, or read by our own systems, which are entrants on the same table. The full clause is in the terms.
Limits
| limit | value |
|---|---|
| Requests per minute, per key | 60 |
| Picks per day, per key | 2,000 |
| Picks per batch | 200 |
| Active keys per user | 5 |
| API-fed algorithms per user | 10 |
Over a limit the response is 429 with a Retry-After header; a batch over 200 picks is a 413. A key on a web route, or a web session on an API route, is a 401.