{"openapi":"3.1.0","info":{"title":"NHL Fantasy Manager","version":"1.0.0"},"paths":{"/health":{"get":{"tags":["system"],"summary":"Health","description":"Liveness + readiness. Never raises; reports degradation honestly.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/status":{"get":{"tags":["system"],"summary":"Status","description":"Current operating mode and safety switches.","operationId":"status_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Status Status Get"}}}}}}},"/sync/preview":{"get":{"tags":["league"],"summary":"Sync Preview","description":"Dry-run a synchronization without writing, to inspect ESPN connectivity.\n\nRequires valid credentials; returns a clear explanation when unconfigured\nrather than attempting a doomed request.","operationId":"sync_preview_sync_preview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Sync Preview Sync Preview Get"}}}}}}},"/api/writes/probe":{"post":{"tags":["system"],"summary":"Writes Probe","description":"Report whether mutating endpoints can actually reach ESPN.\n\nCaddy answers 403 for every write path, so this POST is only ever seen by\nthe app when the caller is NOT behind the edge -- that is, internal.\n\n# PATCH_WRITES_PROBE_HEALTH This used to `return {\"reachable\": True,\n\"writes\": True}` as a literal, so the dashboard showed writes as\navailable no matter what. It reported \"writes: true\" while the autopilot\nhad 75 consecutive failures and zero successful actions in 7 days. A\nhealth signal that is a constant can never go red.\n\nIt now derives a verdict from the write attempts actually recorded in\n`audit_logs`. It deliberately performs NO write: probing by writing would\nmutate the operator's real roster, which is the one thing a health check\nmust never do.","operationId":"writes_probe_api_writes_probe_post","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":7,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Writes Probe Api Writes Probe Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/browser/status":{"get":{"tags":["system"],"summary":"Browser Status","description":"Report ESPN browser session state without performing any action.\n\nRead-only: navigates and inspects, never clicks or submits. Returns\n`authenticated: false` with a specific state so the operator knows whether\nto log in, complete MFA, or wait out a CAPTCHA.","operationId":"browser_status_browser_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Browser Status Browser Status Get"}}}}}}},"/api/control/mode/{mode}":{"post":{"tags":["control"],"summary":"Set Mode","description":"Switch autonomous operating mode at runtime.","operationId":"set_mode_api_control_mode__mode__post","parameters":[{"name":"mode","in":"path","required":true,"schema":{"type":"string","title":"Mode"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Mode Api Control Mode  Mode  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/control/kill-switch/{state}":{"post":{"tags":["control"],"summary":"Set Kill Switch","description":"Emergency stop. `on` blocks all transactions, data collection continues.","operationId":"set_kill_switch_api_control_kill_switch__state__post","parameters":[{"name":"state","in":"path","required":true,"schema":{"type":"string","title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Kill Switch Api Control Kill Switch  State  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/control/pause/{category}":{"post":{"tags":["control"],"summary":"Set Pause","description":"Pause or resume a transaction category (waivers/trades/lineup/all).","operationId":"set_pause_api_control_pause__category__post","parameters":[{"name":"category","in":"path","required":true,"schema":{"type":"string","title":"Category"}},{"name":"enabled","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Enabled"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Set Pause Api Control Pause  Category  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/activity/live":{"get":{"tags":["activity"],"summary":"Live Status","description":"One call for the live dashboard: mode, gates, autopilot, health, freshness.","operationId":"live_status_api_activity_live_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Live Status Api Activity Live Get"}}}}}}},"/api/activity/feed":{"get":{"tags":["activity"],"summary":"Feed","description":"What the bot did and observed, merged, newest first.","operationId":"feed_api_activity_feed_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":400,"minimum":1,"default":150,"title":"Limit"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"default":14,"title":"Days"}},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Feed Api Activity Feed Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/activity/screenshot.png":{"get":{"tags":["activity"],"summary":"Screenshot","description":"Live PNG of the real ESPN page from the signed-in browser profile.","operationId":"screenshot_api_activity_screenshot_png_get","parameters":[{"name":"view","in":"query","required":false,"schema":{"type":"string","default":"league","title":"View"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/activity/event":{"post":{"tags":["activity"],"summary":"Record Event","description":"Record a bot event from host-side cron scripts (briefs, steward).","operationId":"record_event_api_activity_event_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Record Event Api Activity Event Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/activity/autopilot/heartbeat":{"post":{"tags":["activity"],"summary":"Autopilot Heartbeat","description":"Report autopilot cron state (called by espn_lineup_autopilot.sh).","operationId":"autopilot_heartbeat_api_activity_autopilot_heartbeat_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Autopilot Heartbeat Api Activity Autopilot Heartbeat Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/activity/lineup/current-vs-recommended":{"get":{"tags":["activity"],"summary":"Current Vs Recommended","description":"What IS on ESPN right now vs what the optimizer WOULD set, if asked.\n\nRead-only: never submits; the submit path stays behind its own gate.","operationId":"current_vs_recommended_api_activity_lineup_current_vs_recommended_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Current Vs Recommended Api Activity Lineup Current Vs Recommended Get"}}}}}}},"/api/ops/cycle":{"post":{"tags":["ops"],"summary":"Run Cycle","description":"Run the full autonomous cycle now: sync, ingest, project, audit.\n\nRead-only with respect to your team. It computes and stores a recommended\nlineup; it does not submit anything. Safe to run repeatedly.","operationId":"run_cycle_api_ops_cycle_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Run Cycle Api Ops Cycle Post"}}}}}}},"/api/ops/status":{"get":{"tags":["ops"],"summary":"Ops Status","description":"What the system currently knows, in one call.","operationId":"ops_status_api_ops_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Ops Status Api Ops Status Get"}}}}}}},"/api/nhl/sync":{"post":{"tags":["nhl"],"summary":"Nhl Sync","description":"Ingest the real NHL schedule: games, teams, volume, rest, congestion.\n\nThis is the data that makes projections opportunity-aware. A player on a\nteam with 12 games left really is worth less than one with 20.","operationId":"nhl_sync_api_nhl_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Nhl Sync Api Nhl Sync Post"}}}}}}},"/api/nhl/schedule":{"get":{"tags":["nhl"],"summary":"Nhl Schedule Summary","description":"Per-team remaining games, next 7/14, rest and congestion.","operationId":"nhl_schedule_summary_api_nhl_schedule_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Nhl Schedule Summary Api Nhl Schedule Get"}}}}}}},"/api/nhl/today":{"get":{"tags":["nhl"],"summary":"Nhl Today","description":"Today's real NHL games, for lineup context.","operationId":"nhl_today_api_nhl_today_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Nhl Today Api Nhl Today Get"}}}}}}},"/api/ai/status":{"get":{"tags":["ai"],"summary":"Ai Status","description":"Whether AI narration is live, and which model is answering.\n\nNever returns the key itself, only whether one is configured.","operationId":"ai_status_api_ai_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Ai Status Api Ai Status Get"}}}}}}},"/api/ai/notifications/status":{"get":{"tags":["ai"],"summary":"Notifications Status","description":"Whether alerts can actually be delivered, and a no-op proof.\n\nThe scheduler runs its jobs regardless, so a silently unconfigured\nnotifier means alerts go nowhere with no error anywhere. This makes that\nstate visible instead of invisible.","operationId":"notifications_status_api_ai_notifications_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Notifications Status Api Ai Notifications Status Get"}}}}}}},"/api/ai/explain":{"post":{"tags":["ai"],"summary":"Ai Explain","description":"Explain computed waiver targets in plain language.\n\nThe numbers come from the deterministic valuation and waiver engines; this\nendpoint only narrates them. It cannot invent a recommendation, and it\nalways returns a usable explanation even if the provider is unreachable.","operationId":"ai_explain_api_ai_explain_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Request"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Ai Explain Api Ai Explain Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/draft/status":{"get":{"tags":["draft"],"summary":"Draft Status","description":"Live draft state. Safe to poll -- this changes nothing.","operationId":"draft_status_api_draft_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Draft Status Api Draft Status Get"}}}}}}},"/api/draft/board":{"get":{"tags":["draft"],"summary":"Draft Board","description":"Available players, best first, per the documented prior.","operationId":"draft_board_api_draft_board_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":60,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Draft Board Api Draft Board Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/draft/picks":{"get":{"tags":["draft"],"summary":"Draft Picks","description":"Everyone's picks so far, with names resolved.","operationId":"draft_picks_api_draft_picks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Draft Picks Api Draft Picks Get"}}}}}}},"/api/draft/pick":{"post":{"tags":["draft"],"summary":"Submit Draft Pick","description":"Draft one player for your team, then verify it actually registered.\n\nRefuses unless it is genuinely your turn. A pick is irreversible, so\nthis never runs on a timer and never guesses a player for you.","operationId":"submit_draft_pick_api_draft_pick_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Payload"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Submit Draft Pick Api Draft Pick Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/discover/leagues":{"get":{"tags":["discover"],"summary":"Discover Leagues","description":"Find the leagues the saved browser profile has actually visited.\n\nSaves the operator from hunting for a league id. Reads only the profile\nthis system created; returns nothing if the profile is absent.","operationId":"discover_leagues_api_discover_leagues_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Discover Leagues Api Discover Leagues Get"}}}}}}},"/api/discover/adopt":{"post":{"tags":["discover"],"summary":"Adopt League","description":"Save league/season, then lift the session cookies out of the profile.\n\nThis completes setup in one step: the operator signs in once in the\nbrowser, and the REST client picks up that same authorized session rather\nthan the operator hunting for cookies in DevTools.","operationId":"adopt_league_api_discover_adopt_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Payload"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Adopt League Api Discover Adopt Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sync/run":{"post":{"tags":["sync"],"summary":"Run Sync","description":"Mirror league, teams, rosters, players and transactions from ESPN.\n\nRead-only with respect to ESPN: this never modifies your team. It writes\nESPN's observed state into the local database so projections and the\nlineup optimizer have something real to work from.","operationId":"run_sync_api_sync_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Run Sync Api Sync Run Post"}}}}}}},"/api/sync/summary":{"get":{"tags":["sync"],"summary":"Sync Summary","description":"What is currently stored locally, so a sync can be verified.","operationId":"sync_summary_api_sync_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Sync Summary Api Sync Summary Get"}}}}}}},"/api/setup/config":{"get":{"tags":["setup"],"summary":"Setup Config","description":"Report what is configured, without ever echoing a secret back.","operationId":"setup_config_api_setup_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Setup Config Api Setup Config Get"}}}}}}},"/api/setup/credentials":{"post":{"tags":["setup"],"summary":"Save Credentials","description":"Persist ESPN credentials to .env so they survive a restart.\n\nValues are validated before being written. The ESPN password is never\naccepted, requested, or stored — only the two session cookies.","operationId":"save_credentials_api_setup_credentials_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Payload"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Save Credentials Api Setup Credentials Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lineup/recommended":{"get":{"tags":["lineup"],"summary":"Recommended Lineup","description":"Compute the optimal lineup WITHOUT submitting it. Always safe.","operationId":"recommended_lineup_api_lineup_recommended_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Recommended Lineup Api Lineup Recommended Get"}}}}}}},"/api/lineup/submit":{"post":{"tags":["lineup"],"summary":"Submit Lineup","description":"Compute and, if `submit=true`, write the lineup to ESPN.\n\nReturns the full execution outcome, including whether ESPN actually\napplied the change. A 2xx from ESPN is not reported as success unless\nthe roster read-back confirms it.","operationId":"submit_lineup_api_lineup_submit_post","parameters":[{"name":"submit","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Submit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Submit Lineup Api Lineup Submit Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lineup/write-gate":{"get":{"tags":["lineup"],"summary":"Write Gate","description":"Would an unattended write actually improve the roster? READ-ONLY.\n\nScores the lineup currently on ESPN and the optimizer's proposal with the\nSAME valuation model (Candidate.adjusted_value: risk penalty plus goalie\nstart probability), then hands both totals to the pure write policy.\n\nComparing the optimizer's risk-adjusted total against a naive raw-points\ntotal would manufacture a fake improvement, so both sides use adjusted\nvalue. The verdict never performs a write; it only reports whether one is\njustified.","operationId":"write_gate_api_lineup_write_gate_get","parameters":[{"name":"margin","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Margin"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Write Gate Api Lineup Write Gate Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roster/gaps":{"get":{"tags":["roster"],"summary":"Roster Gaps","description":"Which positions the league needs and the roster cannot supply.\n\nA roster that cannot fill the league's own slot template makes every\nlineup submission impossible, because the optimizer has nobody left to\nput in the empty slots. This returns the shortfall by position together\nwith ranked free agents who would close it.","operationId":"roster_gaps_api_roster_gaps_get","parameters":[{"name":"per_gap","in":"query","required":false,"schema":{"type":"integer","maximum":25,"minimum":1,"default":8,"title":"Per Gap"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Roster Gaps Api Roster Gaps Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/roster/waivers/plan":{"get":{"tags":["roster"],"summary":"Roster Waiver Plan","description":"DRY RUN. The exact add/drop calls that would fix the roster's gaps.\n\nSends nothing. Every step is rendered as the precise HTTP method, path\nand body, so the action can be reviewed -- and the write path checked --\nbefore anything irreversible is attempted.","operationId":"roster_waiver_plan_api_roster_waivers_plan_get","parameters":[{"name":"per_gap","in":"query","required":false,"schema":{"type":"integer","maximum":25,"minimum":1,"default":8,"title":"Per Gap"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Roster Waiver Plan Api Roster Waivers Plan Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}