Skip to main content

Error format

Every error is {"error": "<code>"} — one snake_case code, no message or details object. Two codes carry a parameter after a colon: invalid_startsAt:<the bad value> and unknown_event:<the bad name>. The API uses exactly four error statuses: 400, 401, 404, 409. It never returns 403. Unexpected server faults surface as plain 500s.

Classification and retry rules

ClassStatusCodesRetry?
Auth401unauthorizedNo. The key is missing, wrong, or revoked. Fix the credential; see Authentication.
Validation400invalid_json, name_required, source_reserved, source_mismatch, no_games, too_many_games, invalid_startsAt(:<value>), invalid_status, externalId_required, positionId_required, https_url_required, unknown_event:<name>, create_failedNot as-is. Fix the input, then re-send. source_mismatch means the body’s source differs from your key’s — omit the field. For batch game imports, remember earlier items may already be persisted — re-post the whole corrected batch with externalIds.
Not found404league_not_found, game_not_found, official_not_found, position_not_found, not_a_member, endpoint_not_foundNo. The id is wrong, the resource is gone, or it’s outside your key’s scope — deliberately indistinguishable. Re-list to re-discover ids; don’t conclude a resource doesn’t exist platform-wide.
Conflict409user_is_organizer, not_an_official, not_a_league_official, no_open_position, position_not_open, position_taken, position_not_claimed, external_id_conflictNot blindly. State disagrees with your intent. Re-fetch, then decide. position_taken means you lost a concurrent claim race — picking a different open position is a safe follow-up. external_id_conflict means that game externalId already belongs to a different league — a game can’t be moved by re-posting; fix the id or the target league.
Transient5xx / network / timeoutYes, with care. Retry with backoff only requests that are safe to repeat: all GETs, and the three upsert writes (create league, create games, add official) with stable externalIds. Everything else — see below.

Idempotency is your retry safety net

Exactly three writes are idempotent upserts on (source, externalId) and safe to repeat: POST /leagues, POST /leagues/{leagueId}/games, POST /leagues/{leagueId}/officials. Always send a stable externalId from an integration — it is what makes a timeout recoverable by simple re-send. Every other write is not idempotent — never auto-retry after an ambiguous failure (timeout, dropped connection) without checking state first:
  • POST /games/{gameId}/assign — its externalId/source fields identify the official; they do not make the assignment idempotent. Re-fetch the game to see whether the claim landed before re-sending.
  • POST /webhooks — registering twice creates two endpoints. GET /webhooks first.
  • Any DELETE.

Rules for AI agents

If you are an agent operating this API (via REST or the MCP tools), follow these rules:
1

Reads are free; writes need intent

List/get calls are always safe. Only call write tools when the user’s request actually requires the change.
2

Summarize and confirm before destructive actions

Before any of the following, present a plain-language summary of exactly what will change (which league/game/official, by name and id) and get explicit confirmation:
  • Cancel a game (DELETE /games/{gameId} / whistle_cancel_game) — cancels open slots.
  • Remove an official (DELETE /leagues/{leagueId}/officials/{userId} / whistle_remove_official) — also reopens every position they’d claimed.
  • Unassign a position (DELETE /games/{gameId}/assign / whistle_unassign_position) — deletes the claim.
  • Delete a webhook (DELETE /webhooks/{webhookId} / whistle_delete_webhook) — removes delivery history; endpoint may belong to another integration.
  • Bulk mutations — any loop that writes to many records (mass game updates, batch official changes). Confirm the batch as a whole, with a count.
3

Never auto-repeat writes without stable identifiers

Repeating a write is safe only when it’s an upsert keyed on (source, externalId). If you don’t have a stable externalId for a record, a repeated POST creates a duplicate — stop and verify state instead of retrying.
4

Treat 409s as information, not failure

A conflict means the world changed (someone claimed the slot, the official was already removed). Re-fetch, report the actual state, and re-plan rather than forcing the original action.
5

Respect the scope boundary

Your key sees only leagues and webhook endpoints in your source namespace (plus explicit grants) — out-of-scope resources answer 404 exactly like nonexistent ones. Don’t interpret a 404 as “gone” and re-create records on that basis alone; re-list what your key can see and reconcile from there.
Never place a real API key in a tool config file, chat message, or code sample. Connect via environment variables as shown in the MCP quickstart.
Last modified on July 10, 2026