# SETTO External API — workflows

The six tasks an organizer asks for most, step by step: four that set a
tournament up, then its groups and knockout draw, then its results. Each step
names the MCP tool first and the HTTP operation after it (same JSON either way;
`references/endpoints.md` has every field). The setup and draw writes need the
«Gestionar torneos» permission (`manage`), recording and clearing a result
«Cargar resultados» (`results`); without it the answer is
`403 INSUFFICIENT_SCOPE`, and the organizer gets a credential with it rather
than you retrying.

Talk to the organizer in Spanish when they write in Spanish, and use the
dashboard's words: «categoría», «pareja», «Sustitución», «Baja», «Borrador»,
«Grupo A», «Eliminatorias», «Confirmar cuadro», «Borrar resultado», «W.O.»,
«Doble W.O.», «BYE». A tool answer that carries a Spanish `mensaje` (on a
warning, or on a game a withdrawal lists) is ready to show as it is.

## Before any write: names become ids

The organizer says «la Copa Primavera», «4a varonil», «la pareja de Juan
Pérez». Every write takes ids (`idx`), so read first:

1. The tournament: `list_tournaments` with `q` (`GET /v1/external/tournaments?q=…`),
   accents and case ignored. An answer with `qIgnored: true` (an older API with
   no name search; over HTTP, an answer without `query`) is NOT filtered: page
   through it or ask for a date.
2. Anything inside it: `search_tournament` (`searchTournament`) with the words
   the organizer used. Hits come ranked, with their kind and context (category,
   round, group, team, players).
3. When the answer says `ambiguous: true` (two or more hits of one kind match
   well), ask the organizer which one they mean, showing what tells them apart
   (category, players). Never pick one yourself.

## «Crear torneo» — create a tournament

1. Ask what is missing: the sport (padel, tennis or pickleball; fútbol is
   created in the dashboard), the first and last day, the timezone (an IANA
   name such as `America/Mexico_City`), and the categories with their modality
   (pairs or singles). The name the organizer gives is the tournament's name.
2. `create_tournament` (`createTournament`) with `divisions`: the first entry
   renames and configures «Categoría 1», every further entry adds a category.
   Give it your own `idempotencyKey`, a fresh unique string for this one
   create; if the call times out or answers `TOURNAMENT_BUSY`, send the same
   call with the same key, never a new one (a new key would create a second
   tournament).
3. The tournament exists as a draft («Borrador») on the FREE plan: 2
   categories, 12 pairs or 24 singles players. More categories than the plan
   holds is refused before anything is written (`DIVISION_LIMIT_REACHED`):
   the organizer upgrades in the dashboard («mejora el plan en el panel»).
4. Read the answer's `warnings`: a `DIVISION_NOT_APPLIED` names a category that
   could not be created or configured while the tournament itself was. Fix it
   with `create_division` or `update_division`, never by creating the
   tournament again.
5. Give the organizer the public link (`tournament.publicUrl`). Publish only
   when they ask: `publish_tournament` (`publishTournament`). It answers
   `readiness`: `required` steps block publishing (at least one category, valid
   dates) and `warnings` are setup steps still open, which never block it. A
   tournament already public answers `changed: false`.

An organizer creates at most 20 tournaments a day through the API
(`429 TOURNAMENT_CREATE_DAILY_CAP`, with `Retry-After`).

## «Inscribir parejas» — register teams

1. Resolve the category (`search_tournament` with `kinds: ["divisions"]`, or
   `list_divisions`).
2. `register_teams` (`registerTeams`) with up to 50 items, each one team: the
   players (two in a pairs category; one alone registers as awaiting a
   partner, «Por confirmar»; one in singles), and optionally `teamName`,
   `status` (`CONFIRMED` by default, or `PENDING`). One team is
   `register_team` (`registerTeam`). No email is sent to anyone.
3. Report, team by team, what `created` holds and what `failed` holds, with the
   reason of each failure (its `code` and `message`). A refused team never
   stops the others, and an earlier team's players count in a later team's
   duplicate check.
4. `DIVISION_FULL` means the category reached its capacity («Cupo»). Ask the
   organizer before sending that team again with `allowOverCapacity: true`:
   going past the capacity is their decision.
5. `PLAYER_ALREADY_REGISTERED` names the player (`details[].index`) and the
   team that already holds them (`teamIdx`). `TEAM_LIMIT_REACHED` is the plan's
   limit: stop and tell the organizer («mejora el plan en el panel»).
   `FIELD_REQUIRED` means the tournament asks a registration question the API
   cannot answer: that team is registered in the dashboard.
6. A failed item with `INTERNAL_ERROR` failed on SETTO's side and may or may
   not have been written: check with `list_teams`, then send that one team
   again in a new call with a new `idempotencyKey`. The batch's own key only
   answers the same result again.

## «Sustituir jugador» — substitute a player

1. Resolve the team and both players: `search_tournament` finds the team and
   the outgoing player (`kinds: ["teams", "players"]`); the incoming player is
   either someone already in SETTO (`inPlayerIdx`, within this credential's
   reach) or a new person (`inPlayer`: first and last name, optionally email and
   phone; a player of this tournament with that email is reused).
2. Confirm with the organizer who leaves and who comes in, by name, and wait
   for an explicit yes.
3. `substitute_player` (`substitutePlayer`) with `outPlayerIdx` and one of
   `inPlayerIdx` / `inPlayer`, and your own `idempotencyKey`.
4. Report the result: the team keeps its place, games, results, standings and
   groups. A team name made of player names follows the change (`renamed`:
   «Juan Pérez / Ana Gómez» becomes «Luis Ruiz / Ana Gómez»); a custom name
   stays. Read out every warning: `PAYMENT_REVIEW` → «revisa los pagos de este
   equipo en el panel» (nothing about payments changed);
   `PLAYER_IN_ANOTHER_CATEGORY` → the incoming player also plays another
   category, so their games may clash.

## «Baja de equipo» — withdraw a team

1. Resolve the team (`search_tournament` with `kinds: ["teams"]`).
2. `preview_team_withdrawal` (`previewTeamWithdrawal`). It writes nothing and
   answers the plan: `played` games keep their results, and each game in
   `decided` has its consequence (each with a Spanish `mensaje` through the MCP
   tools):
   - `FORFEIT` — a W.O. for the known rival («W.O. a favor de …»);
   - `BYE_ON_ARRIVAL` — a knockout game whose rival is still to be decided:
     that rival passes straight through when it arrives («pasa directo (BYE)»);
     a placement game left with nobody stays an empty bye;
   - `BYE_NOW` — a placement game the team would only have reached by losing
     (third place, say; `via` names the game that feeds it) is decided at once
     as a pass for the team already there;
   - `RECORD_LATER` — nothing is written for that game: its rival comes from a
     group placement, is withdrawn, or is missing, and the organizer records
     the W.O. in the dashboard once the rival is known.
3. Show the organizer every listed game with its consequence, and wait for an
   explicit yes. Never withdraw a team the organizer has not seen the plan for.
4. `withdraw_team` (`withdrawTeam`) with the preview's `confirmationToken`,
   exactly as it came (it lasts 15 minutes and belongs to this team and this
   plan), and your own `idempotencyKey`. `PREVIEW_STALE` means the tournament
   changed since the preview: preview again and show the new plan;
   `CONFIRMATION_EXPIRED` too.
5. Report what happened: the W.O.s (`forfeited`), the games still waiting for a
   rival (`awaitingOpponent`), the passes decided at once (`resolvedByes`), the
   games kept (`kept`), and that the registration was cancelled; the team's
   pending partner invites and unpaid payment links close with it. The team
   keeps its standings rows and leaves its place in the category, but still
   counts toward the plan's capacity: a withdrawal frees no plan place.
6. A team with no games yet has nothing to withdraw from: offer to delete it
   instead (`remove_team`, `deleteTeam`, after the organizer's yes; it frees its
   place in the plan). A team with any payment trace (a payment, a paid mark or
   a payment link) is not deleted through the API (`TEAM_HAS_PAYMENTS`): the
   organizer deletes it in the dashboard, where any refund is made.
   `WITHDRAWAL_UNSUPPORTED` means the dashboard handles this one (a qualifying
   round still to play, or a W.O. that would advance into a game already
   played).

## «Armar grupos / cuadro» — groups and the knockout draw

1. Resolve the category (`search_tournament` with `kinds: ["divisions"]`, or
   `list_divisions`) and read its rounds: `list_divisions` with
   `populate: ["rounds"]` answers each round's `idx`, `type` and `label`.
2. The format, when the category has none or the organizer wants another one:
   `preview_division_format` (`previewDivisionFormat`) with the rounds in play
   order, a group stage («Fase de Grupos», `ROUND_ROBIN_ROUND`), a knockout
   («Eliminatorias», `SINGLE_ELIMINATION_ROUND`) or both, each with its sets
   (1, 2 or 5). It writes nothing. Show the organizer the plan: each round
   `KEPT` or `NEW`, and every round it replaces with its games
   (`plan.replaced`). Wait for an explicit yes, then `apply_division_format`
   (`applyDivisionFormat`) with the preview's `confirmationToken`, exactly as
   it came, and your own `idempotencyKey`. A category that already plays its
   group stage and only lacks the knockout takes `append_elimination_round`
   (`appendEliminationRound`) instead: every group game stays.
3. The groups: `preview_pools` (`previewPools`) on the group-stage round, with
   `numberOfPools`, and `gamesPerTeam` when the organizer says how many games
   each team plays («¿Cuántos partidos por equipo?»). Show the organizer each
   group with its teams, and what it replaces (`plan.replaced`, the knockout
   games included); wait for an explicit yes. «Mueve a Los Tigres al Grupo B»
   is a new `preview_pools` with `pins: [{ teamIdx, pool: 2 }]` and the same
   `rngSeed` the first preview answered (`plan.rngSeed`), so the rest of the
   lot stays as shown: show it again. Then `apply_pools` (`applyPools`) with
   the last preview's `confirmationToken`.
4. The knockout draw: `preview_draw` (`previewDraw`) on the knockout round.
   After a group stage it needs `numberOfTeams` (8: the first two of four
   groups) and takes `method`: «Clasificación entre grupos»
   (`POOL_RANK_PLACEMENT`, the default) or «Clasificación de grupos»
   (`POOL_NUMBER_AND_RANK`, a group's 1st and 2nd in opposite halves);
   `thirdPlace: true` adds the third-place game. Show the organizer the first
   round game by game, with its places («1ro Pool A», «2do de 1ros»: each
   slot's `labelEs`) or teams, and its byes, plus what it replaces; wait for an
   explicit yes; then `apply_draw` (`applyDraw`) with the preview's
   `confirmationToken`. A straight knockout (no group stage) is seated by this
   apply, and that is all.
5. Once the group stage is over, `confirm_draw` (`confirmDraw`,
   «Confirmar cuadro») seats the teams the final standings name. `ROUND_ROBIN_INCOMPLETE`
   lists the games still without a result: confirm once they are in. A first
   round already fully seated, by an earlier confirmation or by hand, answers
   `changed: false` and writes nothing: its teams may sit in another order than
   the standings give, so show the organizer the draw (`get_round`).
   `DRAW_ALREADY_CONFIRMED` means the organizer seated part of it by hand:
   leave it, or, on their word, fill the rest with `set_game_teams`.
6. `DRAW_SEATS_WITHDRAWN_TEAM` names a withdrawn team («Baja») the standings
   put in a slot: ask the organizer who takes its place, a team or a BYE
   (another accepted team of the category the draw does not seat already, or a
   BYE: its opponent goes straight through). Then `confirm_draw` again with
   `replace` (`[{ teamIdx, withTeamIdx }]`, `withTeamIdx: null` for the BYE).
   Never pick the replacement yourself; the same confirmation sent again,
   `replace` included, answers `changed: false` and writes nothing, and a retry
   after a timeout still reuses its `idempotencyKey`.
7. A game's teams by hand (`set_game_teams`, `setGameTeams`), when the
   organizer asks for a different matchup: a first-round game whose other side
   is still a group place («1ro Pool A») takes or drops both teams in one call;
   one side alone is refused (`GAME_NOT_PLAYABLE`, reason `AWAITING_TEAM`), and
   leaving the places to `confirm_draw` seats them from the standings. `null`
   empties a slot, which shows its label again («Ganador del juego N»,
   «1ro Pool A»).
8. At any apply, `PREVIEW_STALE` means the category changed since the preview:
   preview again and show the new plan. `FORMAT_HAS_PLAYED_GAMES`,
   `ROUND_HAS_PLAYED_GAMES` and `KNOCKOUT_HAS_PLAYED_GAMES` mean results stand
   in the way: tell the organizer; clearing them is their decision.

## «Cargar resultados» — enter results

1. Find the game. `get_round` (`getRound`) on its round, with
   `populate: ["games", "games.homeTeam", "games.awayTeam"]` in a group stage
   or the knockout's `draws.rounds.games` (with `homeTeam` and `awayTeam`) in a
   knockout, or `get_schedule` (`getTournamentSchedule`) for a day's games.
   Match the names the organizer says («Pérez / López contra Ruiz / Gómez») to
   one game and its two sides; when two games fit, ask which one.
2. `record_game_result` (`recordGameResult`) with the sets in that game's
   home/away order: «6-3 6-4 para Pérez / López», with Pérez / López as the
   away team, is `sets: [{ home: 3, away: 6 }, { home: 4, away: 6 }]`, and the
   winner follows from the sets. A W.O. is `status: "FORFEIT"` with
   `winnerTeamIdx`; a retirement `FINISHED` with `retiredTeamIdx` and the sets
   as played; «Doble W.O.» `FORFEIT` with `doubleForfeit: true`, in a group
   stage only. Your own `idempotencyKey`, one per result.
3. Tell the organizer what changed: in a group stage the standings
   (`get_round` with `populate: ["pools.standings", "pools.standings.team"]`),
   in a knockout who moved on (`advanced`). Read out each warning:
   `SET_COUNT_UNUSUAL` (the sets do not fit the round's format: check the
   score), `BRACKET_ALREADY_CONFIRMED` (a group result changed after the
   knockout was confirmed: check its matchups in the dashboard),
   `STANDINGS_NOT_COUNTED` (the tournament has no points table yet).
4. A correction: `GAME_HAS_RESULT` means the game already has a result. Show
   the organizer the stored one (`get_round` with `games.score` and
   `games.score.sets`) and ask; only with their yes send the result again with
   `overwrite: true`. The same result sent twice changes nothing.
   `RESULT_PENDING_APPROVAL` means a player's result waits for the
   organizer's approval («Por validar»): show the organizer the player's
   result the same way and ask; only with their yes send the organizer's
   result with `overwrite: true`, or clear it (step 5). Approving the
   player's result as it stands is done in the dashboard.
5. «Borrar resultado»: after the organizer's yes, `clear_game_result`
   (`clearGameResult`; the MCP tool clears only with `confirm: true`). The game
   goes back to «Por jugar» and each next-game slot it filled back to
   «Ganador del juego N» (`reset`). `NEXT_GAME_ALREADY_PLAYED` means a later
   game already holds a result: that one is cleared first, on the organizer's
   word.
6. A game with a withdrawn team keeps its W.O. (`GAME_HAS_WITHDRAWN_TEAM`),
   and a game whose side is still to be decided waits (`GAME_NOT_PLAYABLE`,
   reason `AWAITING_TEAM`).

## What none of these do

They send no email, never create or delete a single game, never touch
qualifiers («Clasificatoria»), never set up consolation games (a result, its
clearing and a game's teams work on any knockout game, third place and
consolation included), never approve or reject a result a player submitted,
and never delete a tournament (it is archived instead:
`set_tournament_lifecycle` with `archive`). Through them no price,
paid mark, payment record or refund changes; a withdrawal closes the team's
unpaid payment links. Every write, and every refusal, is recorded in
«Actividad» (`list_activity`).
