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
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— itsexternalId/sourcefields 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 /webhooksfirst.- 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.
