OPENHOOPS / 2K14Final team archive · November 9, 2026Reference libraryChecksums

Historical API55 integration guide. Use the final dev58 OpenAPI and current handoff for crowd additions and current limits.

Connect a dynasty app

The app can use any language. It starts one Windows broker process and talks to authenticated HTTP on loopback. OpenHoops owns the native match lifecycle; your app owns leagues, careers, schedules, standings and its database. No first-party launcher GUI is required.

What the user sees

Your app's Play button prepares a request and launches OpenHoops. A neutral loading cover appears; apps may customize its title, colors and local images. OpenHoops prepares the roster, rules, participants, uniforms, arena and settings behind that cover, binds external identities before play, and reveals the requested native match. It generates no keyboard/mouse input and does not show a tour through game menus. Gameplay, native menus and current in-game presentation remain NBA 2K14; replacing broadcast graphics is a separate unfinished feature.

Process contract

Create the immutable request file and a new private state path for each match. Persist their association with the app's match ID before spawning anything. Use argument arrays rather than shell command interpolation:

Executable: C:\Tools\OpenHoops-Team\runtime\python.exe
Arguments:
  C:\Tools\OpenHoops-Team\run_openhoops.py
  api
  --launch-game
  C:\Games\OpenHoops\nba2k14.exe
  --match-request
  C:\MyDynasty\requests\game-001.json
  --state
  C:\MyDynasty\sessions\game-001
  --port
  27614
Working directory: C:\Tools\OpenHoops-Team

Equivalent terminal command:

.\OpenHoops.cmd api --launch-game "C:\Games\OpenHoops\nba2k14.exe" --match-request "C:\MyDynasty\requests\game-001.json" --state "C:\MyDynasty\sessions\game-001" --port 27614

The wrapper selects its bundled roster writer automatically. The embedded runtime ignores PYTHONPATH; use the wrapper or explicitly add the bundle's api directory inside your Python program. Each broker owns one child; do not launch another game against the same mutable game/save directory while that child exists.

Redirect stdout/stderr to an app-private log. The broker prints a JSON listening receipt containing its loopback URL and the token-file path, not the token. Keep <state>/token, the state database and logs private. Read the token only in the trusted desktop/backend process; send Authorization: Bearer <token> on every HTTP call. Do not place it in URLs, renderer JavaScript, roster files or shared bug reports. A public website needs a trusted local bridge; the game API is not a public web service.

Use one free loopback port per broker. If using a fixed port, refuse a collision and retain the failed launch state rather than attaching to an unrelated service. Wait for both the child broker to remain alive and an authenticated readiness response. Startup commands can occupy the engine lock for up to 45 seconds, so use a 60-second observation timeout during preparation.

Read the request receipt

GET /v1/launch/request?request_id=game-001
Authorization: Bearer YOUR_PRIVATE_TOKEN

The response is {available, request}. Receipt states are accepted, preparing, starting, started, failed. started:true establishes native Start plus requested pre-play identity binding. It does not establish a finished game. A failure can retain authored roster files and earlier successful commands. started:null means the Start outcome is unknown; preserve it and inspect saved command IDs through GET /v1/commands/{id}. Never issue another Start with a new ID just because its response was lost.

Use GET /v1/lifecycle, GET /v1/matches/{id} and GET /v1/snapshot for current state. The launch receipt remains historical after the game ends. The session and snapshot advertise capabilities; do not assume all features are available on a mismatched hook.

Live data and controls

Read snapshots for clocks, players, totals, physical holder, legal-state validity and presentation observations. Use events/SSE with a durable event cursor for changes. A sequence gap requires recovery; it is not evidence that nothing happened. Controls covers leases, preconditions, coaching and substitutions. Metadata covers team/player fields and event/round context for future scorebugs.

Finish without losing or duplicating a result

  1. Poll GET /v1/results?consumer_id=my-dynasty or use a supplied inbox helper.
  2. Commit each immutable (match_id, result_revision) in the app's durable database before acknowledging it. Store the full result and its idempotency key. A newer revision reconciles previous totals; do not add the entire match again.
  3. Send POST /v1/results/ack with {consumer_id, match_id, revision} after that commit. Lost ACK responses are recovered from the durable inbox and safely acknowledged again. Separate consumers acknowledge independently.
  4. Apply that inbox revision to your league transaction idempotently. An ACK means your app has stored the result; it need not mean every derived screen has refreshed.
  5. Optionally close the matching owned game using the session.stop command with reason:"finished". First verify lifecycle run/match IDs agree with the stored result. This requires a control lease and a unique persisted command ID. The Python ResultInbox.finish_owned and the JavaScript example implement this sequence.

session.stop is a command sent to POST /v1/commands; there is no separate /session/stop HTTP endpoint. reason:"cancelled" abandons an unfinished match without inventing a final result. Closing the broker normally leaves its game open. An app should expose explicit cancel/recovery behavior; retain state instead of killing arbitrary game processes.

Working examples

The bundled Python runtime needs no dependency installation:

.\OpenHoops.cmd example dynasty-session --game "C:\Games\OpenHoops\nba2k14.exe" --request "C:\MyDynasty\requests\game-001.json" --state "C:\MyDynasty\sessions\game-001" --inbox "C:\MyDynasty\results.sqlite3" --consumer-id my-dynasty

This demonstration creates the state directory exclusively, launches, waits, stores/ACKs the result and closes the matching finished game. On failure it retains the log, receipts and state; it closes its broker but leaves an unfinished game open. Adapt its explicit lifetime handling to your app. Do not call this launch helper again to recover an existing match.

For an Electron/Node desktop main process using Node 24+:

import {playMatch} from './examples/dynasty-session.mjs';
const receipt = await playMatch({
  bundle:'C:/Tools/OpenHoops-Team', game:'C:/Games/OpenHoops/nba2k14.exe',
  request:'C:/MyDynasty/requests/game-001.json', state:'C:/MyDynasty/sessions/game-001',
  consumerId:'my-dynasty', inboxPath:'C:/MyDynasty/results.sqlite3',
  onSnapshot: snapshot => updateYourApp(snapshot)
});
// Import receipt.result into the league database idempotently.

The compiled SDK is bundled at sdks/typescript/dist; no npm install is required to import it. Source and declarations are included for other build systems. C#, Rust, C++ and other clients can use the identical HTTP/OpenAPI contract and process arguments. The native C SDK is for in-process mods, not a prerequisite for a dynasty app.

Start with game-free scenarios, then validate your own roster and request, and finally run an isolated real match. See Recovery before implementing automatic retries.