Read-only · JSON · CORS open

LOSGL API v1

Every number on this site comes from these files. Static JSON, regenerated on publish, served with Access-Control-Allow-Origin: *. No auth, no rate limit beyond Cloudflare's, no writes. Cache for at least five minutes — the data only moves when we publish.

Base

https://losgl.thebatcave.cloud/api/v1/

For your AI agent: hand it /api/llms.txt — every endpoint, field, rule, error code and two ready recipes (Google Sheet → survivor sync, ranking submission) in one plain-text file. Machine schema: /api/openapi.json (OpenAPI 3.1).

All endpoints are GET. Responses are UTF-8 JSON. Fields marked nullable are null when the data is missing — never zero, never invented. Check status on matchups before using scores.

curl -s https://losgl.thebatcave.cloud/api/v1/published.json
# {"published_at":"2026-…Z","published_human":"…","content_hash":"b702d9805a35baeb…"}

Endpoints

PathWhat
published.jsonLast publish time + content hash. Poll this (no-store) to know when anything changed.
site.jsonLeague meta: name, league_name, current_season, last_complete_season, seasons[] (year, available, complete, champion, last_place, latest_week), sync (ok, auth_expired, status), status/bug vocab.
seasons/{year}.jsonOne season: standings[] (wins, losses, ties, pf, pa, allplay_w/l/t, ppg, streak, rank, final_rank, manager_slug, franchise_slug), weeks[]tickets[] (matchup_id, week, tier, is_playoff, status, home/away {name, manager, manager_slug, points, result, projected, box}, margin, winner, line), playoffs[], consolation[], byes[], draft.rounds[], draft_report, outcomes (champion, runner_up, last_place, champion_verification), gaps[], rule_change.
managers.jsonEvery manager: slug, name, aliases[] (ESPN handles only), seasons[], career, playoffs, titles[], last_places[], rivals[].
franchises.jsonFranchise lineages: slug, name, names[] (every team name used), owners[] (manager_slug, start, end), seasons[], career.
h2h.jsonmanager and franchise maps keyed "a-slug|b-slug": record.all/regular/playoff as [W, L, T] from A's side, games[], pf_a, pf_b, streak_a, line. Consolation games excluded.
records.jsonThe record book: rules + categories[] (key, title, scope, items[] with matchup_id/year/week links).
rankings/{year}.jsonstatistical[] (one entry per week: rank, score, components, delta) and commissioner[] (published editions only: rank, comment, delta). formula documents the math.
draft/{year}.jsonDraft report card: teams[] (points = season fantasy points produced by that team's picks, best, bust, reach, steal), late_gems[], reaches[], steals[], coverage, rules. Derived from FantasyPros consensus + season points; only our derived numbers are published.
notes.jsonPublished commissioner notes (metadata + excerpt; full HTML lives at /notes/{slug}/).
sleepers.jsonSleepers: per-season top 5 late-round/undrafted finds ranked by points over positional replacement, with who found them; all-time "sleeper hunter" board.
transactions/{year}.jsonThe Wire: faab[] (remaining/spent per team), weeks[] with waivers_won, waivers_failed (with reason), free_agents, trades (outcome, pieces). Archive starts 2026-09-14 (ESPN only serves the current season).
survivor/{year}.jsonSurvivor pool snapshot synced from the commissioner's sheet: participants[] (name, alive, eliminated_week, picks by week with NFL code + result), alive_count, winner.
nfl/schedule/{year}.jsonNFL schedule by week from ESPN's public scoreboard: kickoff (UTC), teams, status, scores, and the line ESPN attaches (spread, favorite, over/under). Raw material for a survivor picker.

Power rankings · read & submit

Read: rankings/{year}.jsonstatistical[] (one object per week: week, entries[] with rank, season_team_id, name, manager_slug, score, components {ppg, allplay, win_pct, form}, delta) and commissioner[] (published editions: week, title, intro, published_at, entries[] with rank, season_team_id, name, manager, comment, delta). season_team_id is the stable key for a team in a season; the full list for a year is in seasons/{year}.json → standings[].season_team_id.

Submit (external ranking systems): the one write endpoint on this hostname. Ask the commissioner for a bearer token; each token is a named source and its editions appear as their own set under Power Rankings — they never overwrite the commissioner's or the formula's.

# who am I / does my token work
curl -H "Authorization: Bearer $TOKEN" https://losgl.thebatcave.cloud/api/v1/submit/whoami

# the season_team_ids you must rank (also in seasons/{year}.json → standings[])
curl -H "Authorization: Bearer $TOKEN" https://losgl.thebatcave.cloud/api/v1/submit/teams/2026

# publish an edition (one entry per team, ranks 1..N each used once; re-POST the same year+week to replace)
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  https://losgl.thebatcave.cloud/api/v1/submit/rankings -d '{
  "year": 2026, "week": 3,
  "title": "Week 3: somebody call his mother",
  "intro": "optional, 2–4 sentences",
  "entries": [
    {"season_team_id": 145, "rank": 1, "comment": "1–3 sentences, optional"},
    {"season_team_id": 152, "rank": 2, "comment": "…"}
  ]
}'
# 201 {"kind":"external:josh","year":2026,"week":3,"entries":12,"public_within":"15 minutes (next publish tick)"}
# 401 bad token · 403 wrong scope · 422 validation message · 413 body over 256 KB · 429 over 30 req/min

Only ranking editions can be written. Comments are plain text (max 1200 chars), titles/intros max 2000; slurs are rejected. The site republishes within one timer tick, so read back rankings/{year}.json after ~15 minutes. Tokens are hashed at rest and can be revoked by the commissioner at any time.

Vocabulary

Matchup status: final (counted everywhere) · provisional (decided, inside the stat-correction window; counted in standings, not in records) · live (in progress; never counted) · scheduled · missing (ESPN has no score; never counted) · bye.

Tier: NONE regular season · WINNERS_BRACKET playoffs · WINNERS_CONSOLATION_LADDER / LOSERS_CONSOLATION_LADDER consolation (excluded from every stat).

Identity: a manager is a person (may span several ESPN accounts); a franchise is the ESPN team slot lineage; a season team is one team in one year with that year's name. Slugs are stable across publishes unless the commissioner renames someone.

Privacy: no ESPN ids, emails, or real names are published — only display handles and team names. Nothing here proxies ESPN or FantasyPros; there is no key to steal.

Versioning: breaking changes get a new /api/v2/; v1 stays. Questions go to the commissioner.