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

  1. 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.
  2. In Settings, mint an API key. The secret is shown once and looks like hk_live_…. Up to five active keys; revoke any time.
  3. Send picks to POST https://hindsight-production-e3c9.up.railway.app/picks with the key as a bearer token. That is the whole integration. A missing or malformed key is a 401 API key required; a well-formed key we do not recognise is a 401 invalid 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.

fieldmeaning
sportA 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_numbergame_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).
eventFree 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.

marketselectionline
moneylinehome · away (or a team name)none
spreadhome · awayhome side's number, e.g. -1.5 for a home favourite
totalover · underthe total, e.g. 8.5
team_totalhome_over · home_under · away_over · away_underthat team's total
f5_moneylinehome · awaynone (first five innings, MLB)
f5_totalover · underthe 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
    }
  ]
}
reasonwhat it means
invalidUnknown market, a selection that does not fit it, a missing line, or a league that is not the algorithm's own.
unknown_algorithmNo API-fed algorithm of yours by that id or name (or the name is ambiguous; use the id).
unmappedAn unknown league code, or no scheduled event matches; for a tracked league only our schedule counts.
ambiguousMore than one event matches; candidates are listed. Add the date or game number.
already_startedThe event's scheduled start has passed on our clock.
duplicateThe same (algorithm, event, market, selection, line) already exists; its id is returned.
rate_limitedPast 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

Limits

limitvalue
Requests per minute, per key60
Picks per day, per key2,000
Picks per batch200
Active keys per user5
API-fed algorithms per user10

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.

Pick API — Hindsight