Demo You're touring a finished example league — everyone here is made up. Join for real →
API ACCESS
This is the DEMO instance. Keys minted here start with demo_ and only work under /demo. For your real league, use the API page at ligazo.app (no /demo). Every API response also carries "instance": "demo".
DEVELOPER API

Build your own tools on the league's draft data — a cheat sheet, a "best available" board, or a script that sets your pick queue for you.

The whole reference below as Markdown — endpoints, response fields, examples. Paste it into a script, a doc, or an AI assistant. (Your key isn't embedded — set it once with the export line.)
YOUR KEY

The demo is read-only — sign in to your own league to mint a key.

ENDPOINTS

Base URL https://ligazo.app/demo. Authenticate with your key as ?key=… or an X-Api-Key header. If a key is presented and it's wrong, you get a 401 even in a signed-in browser — a bad key never silently falls back to your session.

GET /api/draft — every league at once (array under leagues).
GET /api/draft/<league> — one league.
GET /api/draft/<league>/status — cheap poll: picks_made, on_the_clock, your_next_pick. Poll this; fetch the big one on change.
GET /api/draft/<league>/queue — your current pick queue, in order.
POST /api/draft/<league>/queue — set your whole queue. Body {"players": ["id1", "id2", …]} in priority order. Ids that aren't draftable (already taken, wrong league, unknown) come back under rejected.
GET /api/rules — the scoring matrix + roster/lineup/armband structure, straight from the engine so it never drifts.
GET /api/standings (or /<league>) — the table: cumulative points per manager, overall and per league, ranked (ties share a rank).
GET /api/fixtures (or /<league>) — real-match Score Center: each game's clubs, score, status (sched/live/ht/ft), minute, kickoff.
Rate limit: 60 reads and 20 writes per minute per key (bursts of 20 / 8). Over that: a 429 with a Retry-After header — pause and retry.
Conditional requests: every GET returns an ETag. Send it back as If-None-Match and you get 304 Not Modified (no body) when nothing changed — cheap polling. Pair it with /status for a near-free refresh loop.
Lazy clock: a read runs the draft clock, so polling advances expired and Auto-Draft turns. Reads are cheap but not side-effect-free; the draft also ticks on a cron, so it moves even if nobody polls.
LEAGUES

<league> is the exact, URL-encoded league name. All five, in draft order (append /queue or /status to any of these):

ESP-La Liga complete
https://ligazo.app/demo/api/draft/ESP-La%20Liga?key=$KEY
ENG-Premier League complete
https://ligazo.app/demo/api/draft/ENG-Premier%20League?key=$KEY
FRA-Ligue 1 complete
https://ligazo.app/demo/api/draft/FRA-Ligue%201?key=$KEY
ITA-Serie A complete
https://ligazo.app/demo/api/draft/ITA-Serie%20A?key=$KEY
GER-Bundesliga complete
https://ligazo.app/demo/api/draft/GER-Bundesliga?key=$KEY
RESPONSE FIELDS

A single league object (the all-leagues call wraps these under leagues:[…] with instance, season, generated_at):

{
  "instance":        "league",          // "league" or "demo" — which server answered
  "generated_at":    "2026-08-09T…Z",
  "league":          "ESP-La Liga",     // exact id — use it in the URL
  "name":            "La Liga",
  "status":          "live",            // pending | live | complete
  "snake":           true,              // draft snakes: even rounds reverse
  "roster_size":     8,
  "picks_made":      4,
  "slots_remaining": 92,
  "available_count": 477,
  "on_the_clock": {                     // null unless status == "live"
    "pick": 5, "round": 1, "manager_id": 12, "manager": "…", "team": "…",
    "deadline": "2026-08-09T16:33:35Z"
  },
  "picks":     [ { "pick": 1, "round": 1, "made_at": "…", "manager_id": 7,
                   "manager": "…", "team": "…", ...player }, … ],
  "managers":  [ { "manager_id": 1, "manager": "…", "team": "…",
                   "seat": 0,       // draft-seat order (0-based)
                   "slots_left": 7,
                   "autopick": false, // on Auto Draft?
                   "roster": [ ...player ] }, … ],
  "available": [ ...player ],          // who's LEFT, best prev-season first
  "you": {                             // present when you call with your key
    "manager_id": 1,
    "your_next_pick": 14,              // your next pick number (null if done) — no snake math
    "autopick": false
  }
}

// ...player =
{ "player_id": "p123", "player": "Kylian Mbappé", "pos": "FWD", "club": "Real Madrid",
  "prev_pts": 152,                     // last season's Ligazo points; 0 for newcomers
  "kind": "player",                    // "player" | "club_gk"
  "display_name": "Kylian Mbappé" }    // for a club-GK: "FC Barcelona (GK)"
Queue endpoint → { instance, league, manager_id, queue:[…player…] }, plus rejected:[…ids…] on a POST. A goalkeeper slot is drafted by club, not person: it comes back as kind: "club_gk" — use display_name and don't parse the raw GK::<club> id.
EXAMPLES
Generate a key above, then export KEY=your-key and the examples below work as-is.
Read every league
curl "https://ligazo.app/demo/api/draft?key=$KEY"
Cheap poll — has anything changed?
curl "https://ligazo.app/demo/api/draft/ESP-La%20Liga/status?key=$KEY"
Read the scoring rules
curl "https://ligazo.app/demo/api/rules?key=$KEY"
The table + real fixtures
curl "https://ligazo.app/demo/api/standings?key=$KEY"
curl "https://ligazo.app/demo/api/fixtures/ESP-La%20Liga?key=$KEY"
Conditional GET — 304 when nothing changed
curl -sD- -o/dev/null -H 'If-None-Match: "<etag-from-last-response>"' \
  "https://ligazo.app/demo/api/draft/ESP-La%20Liga?key=$KEY"
See who's left in ESP-La Liga
curl "https://ligazo.app/demo/api/draft/ESP-La%20Liga?key=$KEY"
Set your ESP-La Liga queue (top 3, in order)
curl -X POST "https://ligazo.app/demo/api/draft/ESP-La%20Liga/queue?key=$KEY" \
  -H "Content-Type: application/json" \
  -d '{"players": ["id1", "id2", "id3"]}'
Python: queue best-available forwards in every pending league
import os, requests, urllib.parse
BASE, KEY = "https://ligazo.app/demo", os.environ["KEY"]
draft = requests.get(f"{BASE}/api/draft", params={"key": KEY}).json()
for lg in draft["leagues"]:
    if lg["status"] == "complete":
        continue
    fwds = [p["player_id"] for p in lg["available"] if p["pos"] == "FWD"][:5]
    path = urllib.parse.quote(lg["league"])          # encodes the space in the name
    requests.post(f"{BASE}/api/draft/{path}/queue",
                  params={"key": KEY}, json={"players": fwds})
    print(lg["league"], "queued", len(fwds))