# League Autopilot agent CLI (`la`) > A small non-interactive CLI that lets an AI agent read and act on a sports > organization's League Autopilot data under the user's own permissions, via a > personal access token. One server gateway runs each command under the token > owner's identity; row-level security is the only permission model. ## Start here - Setup: set `LA_URL` (the Supabase project URL, required). The credential is a sign-in the USER makes once with `la login` (it opens their browser; never run it yourself), or `LA_TOKEN` (a personal access token, `lap_…`), which wins when set. With neither, every command exits 3 with an auth error that says `Run "la login"`. - Discovery: run `la schema` (no auth/config/network) for every command, flag, error kind, and exit code. Never guess flags — an undeclared flag is refused (exit 2) with the closest real one named. Booleans take `--flag`, `--flag true`, or `--flag false`. - Docs with no credential: `docs search` and `docs get` run with no token, sign-in or sandbox key. - Credential order: `LA_TOKEN`, then the `la login` sign-in, then a stored sandbox key. - Full agent guide: see SKILL.md in this package. ## Sandbox (on the roadmap; switched off for now) - A sandbox is an org you build with no account, for a user who asked for one. Until sandboxes open, `sandbox create` answers `org_access_off` (exit 4). - `la sandbox create --name --time-zone --players-under-18 yes|no|unsure --user-asked [--client-name ] [--replaces las_…]`. Pass `--user-asked` only when your user asked; without it nothing is sent (exit 2). Starting a sandbox accepts the Terms (https://leagueautopilot.com/terms); see the Privacy Policy (https://leagueautopilot.com/privacy). - A busy platform sends a proof-of-work check; the CLI solves it by itself. - The reply (JSON) holds the preview link, the claim link, the limits and `next_steps`. Give the claim link to your user once. It is never shown again. - The key (`las_…`) is stored for `LA_URL` in the credentials file. `la sandbox status` shows the time left, the limits and the preview link. `la sandbox forget` removes the key (no network call). - Limits: 1 league, 4 divisions, 8 teams, 150 people, 200 events, 2 seasons, 10 venues. Past a limit, or for invites, announcements or server AI: `sandbox_limit`, exit 4. Claiming lifts it; do not retry. - A missing tool: `la report-gap --tool --need ` (after `la login`, or with a sandbox key). ## Output contract - Data on stdout only; diagnostics + the error envelope on stderr. - `--json`, `--ndjson`, `--fields a,b,c`. Lists carry a `truncated` flag (cap 50). - Money is integer cents; dates ISO; ids UUIDs with human names alongside. ## Commands - Reads: `whoami`, `org overview`, `events list`, `events rsvps `, `events unanswered`, `teams roster `, `members list`, `tasks list`, `finances team `, `finances org`, `venues list`, `search `, `docs get `, `docs search `, `key-dates list`, `seasons list`, `seasons enrollments `, `seasons standings`, `seasons costs `, `lineup show `, `lineup candidates `. - Writes (read-and-write token; accept `--idempotency-key` and `--dry-run`; an empty, value-less, or >200-char key is exit 2, never silently ignored): `events create`, `events set-rsvp `, `events nudge `, `tasks create`, `tasks update `, `teams create`, `teams add-member`, `seasons create`, `seasons add-cost`, `seasons update-cost `, `seasons remove-cost `, `seasons costs-config `, `finances record-payment`, `announcements send`, `members invite`, `members send-invite`, `members update-contact`, `members import`, `members import-check`, `members import-commit`, `members import-send-invites `, `lineup set-slot `, `key-dates add`, `key-dates remove`, `structure add-league`, `structure add-division`, `venues create`. - `--dry-run --json` returns `{"dry_run":true,"valid":true,"input":{…}}`: the input the real run would send, defaults filled in. Show it to the user before the write. - A member add (`teams add-member`, `members invite`) needs `--invite` or `--no-invite`: there is no default, and a call with neither is `invalid_input`. Run it with `--dry-run` first: it checks your permission and prints the details (person, team and roster, new or existing member, invite choice). Show them to the user and get a yes before the real call. `members send-invite` sends an invite later, or sends it again. - A list of people: `members import --file roster.csv --dry-run --json` checks a CSV file (header: name, email, phone, team, division, league, list) and prints a `plan_id`, a `version`, counts and issues by file line. Nothing is added. Fix the file and check it into the same plan with `--file roster.csv --plan `. Then `members import --plan --plan-version --invite|--no-invite` adds every row or none. The file must be UTF-8. At most 500 rows a file. `members import-plan ` shows invite progress, and `members send-invite --import ` queues a whole import's invites later. The CLI reads the file itself: never copy rows into a command. ## Tenant setup - Order: `structure add-league`, then `structure add-division`, then `teams create`, `venues create`, `seasons create`, `events create`. Each create returns the new id. - `structure add-league --name --shape formal|informal [--sport ]`. Org admin, or the `structure` capability. `--shape` is required: billing is per active team and depends on the shape. Sport defaults to Soccer. Play settings stay in the app. - `structure add-division --league --name `. League admin over that league, org admin, or the `structure` capability. A division admin gets `scope`. - `venues create --name --scope org|league|team [--league ] [--team ] [--sub-location ] [--address ]`. Org venue: org admin. League venue: league admin over it. Team venue: a captain of that team. A division admin gets `scope`. Permits are recorded with `venues add-permit` (MCP). - None of the three notifies anyone. A league or team in another org is `not_found`. - `events create --quiet` skips the event-created notification. Use it to seed a future schedule. Only a division admin or higher over the team, or a holder of the `scheduling` capability, may pass it. A captain gets `scope` and nothing is created. Without `--quiet`, the team's core roster and captains are notified. - `events create-batch` creates up to 100 events in one request, and counts as one write against the hourly limit. Each event carries its own `key`; an event already made with that key, by either command, is skipped, so re-running a stopped batch finishes it. The first failure stops the batch and names the event. ## Tasks - `tasks update` is patch-only: just the flags you pass change. `--due-at ""` clears a date; a title cannot be cleared. - `--status` follows the DB transition table (`blocked` cannot go straight to `done`). An illegal move is refused with the legal ones listed and writes nothing else in that command. - To correct a task you created, use `tasks update`. A re-`create` is a create: the same idempotency key replays (`changed:false`, nothing written) and a new key makes a SECOND task. Every create matches the key exactly, never the fields. - `--due-at` / `--start-at`: pass a plain `YYYY-MM-DD`; the server anchors it to midday UTC. Do NOT pass midnight UTC — a due date is a real instant rendered on the viewer's local day, so `T00:00:00Z` shows as the day before west of UTC. Use a full ISO date-time only when the time matters, with its offset. `key-dates add --date` is a bare calendar date and never shifts. ## Seasons - A season is a league's dated cycle of play. Games require one; practices may not. - "Current" is NOT status `active`: it is the league's default season, falling back to the latest-starting active one. Read `is_current` from `seasons list`, or `current_season` from `org overview`. Overlapping seasons are legal. - `seasons create` takes `--status draft` (default), `upcoming`, or `archived` for a season that has ended (then `--starts-on` and `--ends-on` are required, and the end must be today or earlier). `active` is refused. The new season never becomes current on its own, enrolls no teams, creates no fees, notifies nobody. - `seasons enroll --season --team [--division ] [--status pending|confirmed]` enrolls one team; the MCP tool `seasons_enroll-batch` takes up to 100 at once, each with its own idempotency `key` (scoped to the season). A confirmed team is skipped, a pending one is confirmed when asked, a withdrawn one is refused. Confirming this way prepares NO fee plan and notifies nobody; fees are set on the season's Costs & Fees tab. A captain with no admin scope is refused. - Set-default, activate/archive, withdrawals and division moves are NOT exposed: each has a side effect an agent would not expect (a retargeted schedule, deleted games). - Facilities: `venues grantors` and `venues permits` read the grantors (a city parks department, say) and the permits with one line per venue (weekly hours, day_of_week 0 = Sunday, excluded dates). `venues add-grantor` adds a grantor (a repeat by name answers with the existing one). The MCP tool `venues_add-permit` records a permit with its lines in one call; a granted permit's hours reach the venue calendars at once. `venues set-permit-status` grants, denies, expires or cancels one. `venues update-grantor` and `venues update-permit` (MCP for the lines) change only the fields passed; a granted permit's calendars follow a changed term or lines, so an added excluded date drops that date. Nobody is notified by a create or an edit. - Application rounds: `venues application-rounds` reads a grantor's booking dates per use period (opens, priority deadline, confirmed, public opens) with the recorded status and the state (upcoming, open, missed, applied, confirmed, skipped). `venues add-application-round` records one; `venues update-application-round` changes the fields passed, and a status of applied, confirmed or skipped stops its reminders. `--round` on `add-permit` and `update-permit` links a permit to a round of its own grantor. - `events create --season ` targets a specific season for a Game. It must be a season of that team's own league — `seasons list` spans the org, so check `league_id`. Omitted, the league's current season is used. No current season → the command fails with a written explanation, not a raw constraint error. - `events list --season ` filters a schedule to one season. ## Season costs (finance managers only) - `seasons costs ` — cost lines per division + shared, division pools, each team's computed fee (live from the current costs + schedule), deposit config, payment status once fees are set, and `differs_from_current_estimate` (drift after a cost/schedule change; nothing recalculates on its own). `--mode` / `--split` preview a different basis; they store nothing. Non-managers get a clear `no_access` message. - `seasons add-cost` (`--division` for one pool, omit for shared; `--category` takes a category NAME, unknown names refused with the list), `seasons update-cost ` (patch-only; `--division ""` makes a line shared), `seasons remove-cost `, `seasons costs-config ` (deposit, due dates, target surplus; `0` clears the deposit, `""` clears a date). - The first cost write creates the season's budget period in Finances (the response notes it). Closed books refuse every write and name the reopen path. - Setting team fees is NOT a CLI command — it notifies every captain, so the user applies fees on the season's Costs & Fees tab in the app. ## Lineup - `lineup show` is computed live by the deterministic engine, not read from a saved snapshot. Games only; captain or admin; needs the AI Captain module. - `lineup candidates` suggests who to invite. It never sends an invite. - `lineup set-slot` places one Core roster player in one slot. A player holds one position at a time, so a move is one command (it frees the slot they were in). A TAKEN slot returns `kind:"conflict"` naming the occupant — relay it to the user, then re-run with `--swap`. Omit `--member` to hand the slot back to the engine. Nobody is notified — a lineup is a plan. - A successful write may return `notes` (stderr `note: …`). Advisory, never a refusal — e.g. the player you placed replied Out. Placement is allowed; relay the note. ## Errors - Envelope: `{"error":{"kind","message","hint"}}` as the last line of stderr. - Exit codes: 0 ok · 2 usage · 3 auth · 4 permission/scope or `sandbox_limit` · 5 rate-limited · 6 not-found · 1 other. Retry only exit 5, after `retry_after`, with jitter. - `kind:"conflict"` (exit 2) means the write needs a decision first. Ask the user, then re-run with the flag the message names.