---
name: setto-api
description: "Read SETTO tournament data (tournaments, divisions/categories, teams, rounds, games, standings) for one organization through the SETTO External API with an org API token. Use when a task mentions SETTO, padel/tennis/pickleball tournaments run on setto.io, or a setto_live_/setto_test_ token."
license: MIT
metadata:
  version: "1.0.0"
  docs: "https://docs.setto.io"
  openapi: "https://docs.setto.io/openapi.json"
  mcp: "https://docs.setto.io/mcp"
---

# SETTO External API

A read-only HTTP API that returns one organization's padel, tennis and
pickleball tournaments as run on setto.io: tournaments, categories (divisions),
teams and players, rounds, groups, brackets, games and standings.

## When to use this skill

Use it when the task involves any of:

- a SETTO tournament, category, team, round, bracket, game or standings table;
- a token that looks like `setto_live_…` or `setto_test_…`;
- a public tournament page under `https://www.setto.io/t/<slug>`;
- embedding SETTO fixtures, draws or results somewhere else.

Do **not** reach for it to create or change anything. There is no write access:
the API cannot create a tournament, submit a score, or register a player. If the
task asks for a mutation, say so and stop.

## Prefer the MCP server when it is available

If a SETTO MCP server is connected (`https://docs.setto.io/mcp`, tools `me`,
`list_tournaments`, `get_tournament`, `list_divisions`, `list_teams`,
`get_round`), call those tools instead of issuing HTTP requests by hand. They
wrap exactly these six endpoints, handle the auth header, and return the same
JSON. Fall back to the raw HTTP calls below only when no such tool is present.

## Authentication

Every request needs an organization API token in an `Authorization` header:

```
Authorization: Bearer $SETTO_API_TOKEN
```

- Read the token from the `SETTO_API_TOKEN` environment variable. If it is not
  set, ask the user to set it — do not ask them to paste the token into the
  conversation.
- `setto_live_…` tokens work against production. `setto_test_…` tokens work only
  against non-production environments.
- A token belongs to an **organization**, not to a person, and it can only read
  tournaments owned by that organization. Personal tournaments are invisible to
  it.
- Tokens are created in `app.setto.io` → **Organización** → the **API** tab, by
  the organization's creator or an `ADMIN` member. The secret is shown once.

## Base URL

```
https://api-production-ea80.up.railway.app/v1
```

Every path below is written in full, so it appends directly to the host. A
friendlier `api.setto.io` host will replace the Railway one later; the paths do
not change.

## The six endpoints

All are `GET`. `idx` is always a UUID v4.

### 1. `GET /v1/external/me` — who the token belongs to

```bash
curl -s https://api-production-ea80.up.railway.app/v1/external/me \
  -H "Authorization: Bearer $SETTO_API_TOKEN"
```

Returns `{ organization, token, rateLimit }`. Cheapest way to confirm a token
before doing real work.

### 2. `GET /v1/external/tournaments` — list tournaments

Query: `status`, `sport`, `populate`, `limit` (1–100, default 25), `offset`.

```bash
curl -s -G https://api-production-ea80.up.railway.app/v1/external/tournaments \
  -H "Authorization: Bearer $SETTO_API_TOKEN" \
  -d status=IN_PROGRESS -d sport=PADEL -d limit=25
```

Returns `{ data: Tournament[], meta: { limit, offset, total } }`, ordered by
`startDate` descending.

### 3. `GET /v1/external/tournaments/{idx}` — one tournament

```bash
curl -s -G "https://api-production-ea80.up.railway.app/v1/external/tournaments/$TOURNAMENT_IDX" \
  -H "Authorization: Bearer $SETTO_API_TOKEN" \
  -d populate=club,divisions
```

### 4. `GET /v1/external/tournaments/{idx}/divisions` — categories

```bash
curl -s -G "https://api-production-ea80.up.railway.app/v1/external/tournaments/$TOURNAMENT_IDX/divisions" \
  -H "Authorization: Bearer $SETTO_API_TOKEN" \
  -d populate=rounds
```

A "division" is what the product calls a *categoría*.

### 5. `GET /v1/external/tournaments/{idx}/teams` — teams and players

Query: `populate`, `division` (a division `idx`, to restrict the result).

```bash
curl -s -G "https://api-production-ea80.up.railway.app/v1/external/tournaments/$TOURNAMENT_IDX/teams" \
  -H "Authorization: Bearer $SETTO_API_TOKEN" \
  -d populate=playerProfiles -d division="$DIVISION_IDX"
```

### 6. `GET /v1/external/rounds/{idx}` — one round, with everything in it

```bash
curl -s -G "https://api-production-ea80.up.railway.app/v1/external/rounds/$ROUND_IDX" \
  -H "Authorization: Bearer $SETTO_API_TOKEN" \
  -d populate=division,pools,pools.standings,games,games.score,games.score.sets
```

This is where groups, standings, games and brackets live. Most integrations
spend their time here. Round `idx` values come from
`populate=divisions.rounds` on a tournament, or `populate=rounds` on divisions.

## Populate rules

- `populate` is a **comma-separated list**, e.g. `populate=club,divisions`.
- A relation appears in the response **only** when you ask for it, or when it is
  in that endpoint's default list.
- A nested path implies its parents: `pools.standings.team` brings `pools` and
  `pools.standings` with it.
- An unknown value is a `400`, and the error message enumerates the legal values
  for that endpoint. Do not guess — the per-endpoint allow-lists are in
  `references/endpoints.md`.
- Ask for the least you need. `getRound` with the full bracket chain is a large
  payload.

## Pagination

Only `GET /v1/external/tournaments` is paginated: `limit` (1–100, default 25)
and `offset` (≥ 0), with `meta.total` giving the full count. Walk pages by
incrementing `offset` by `limit` until `offset + data.length >= meta.total`.
The other five endpoints return their whole collection.

## Errors — and what to do about each

Body shape: `{ statusCode, code, message }`. Branch on `code`, never on
`message`.

| Status | `code` | What to do |
| --- | --- | --- |
| `400` | `INVALID_ID` | A path `idx` is not a v4 UUID. Rejected before the handler, so it is **never** a `404` — fix the id, do not report "not found". |
| `400` | *(validation; `message` is a string array)* | Fix the request. Usually a bad `populate` value, `limit` outside 1–100, or a non-UUID `division`. Never retry unchanged. |
| `401` | `MISSING_API_TOKEN`, `INVALID_API_TOKEN`, `EXPIRED_API_TOKEN` | Stop and ask the user for a new token — do not retry, and do not try other tokens. |
| `403` | `INSUFFICIENT_SCOPE` | The token cannot read this route. Stop and tell the user. |
| `404` | `TOURNAMENT_NOT_FOUND`, `ROUND_NOT_FOUND` | Wrong id, or the resource belongs to another organization. The API never reveals which. Re-check the id and the token's organization. |
| `429` | `RATE_LIMITED` | Back off for the `Retry-After` seconds, then retry once. |
| `5xx` | — | Retry with exponential backoff and jitter. |

Full table with every field: `references/errors.md`.

## Rate limits

120 requests per 60 seconds per token, plus a pre-auth ceiling of 300 requests
per 60 seconds per IP. Every authenticated response carries `X-RateLimit-Limit`,
`X-RateLimit-Remaining` and `X-RateLimit-Reset` (unix seconds); a `429` adds
`Retry-After` (the IP ceiling sends `Retry-After` only). Responses have strong
ETags — send `If-None-Match` and an unchanged read answers `304` with no body,
which saves bandwidth but still spends one request of the budget.

## Safety

- **Never print, echo, log or commit the token.** Read it from
  `SETTO_API_TOKEN`; in a shell, pass it as `"Bearer $SETTO_API_TOKEN"` so the
  value never appears in a transcript.
- Never write the token into a file the user did not ask for, and never into a
  client-side bundle — a browser-visible token is readable by anyone.
- The API is read-only. Nothing in this skill can change SETTO data.
- The API never returns personal contact data (no emails, no phone numbers) and
  no payment data. Player objects carry only `idx`, `firstName`, `lastName` and
  `avatar`. If a task needs more than that, the answer is that the API does not
  expose it.
- Two things the API *does* return and people trip over: hidden divisions and
  rounds come back with `isVisible: false` rather than being dropped, and
  `DRAFT`/`ARCHIVED` tournaments are included unless you filter on `status`.

## References

- `references/endpoints.md` — every parameter, every legal `populate` value per
  endpoint, and the main response fields.
- `references/errors.md` — every error code, cause and remedy.
- Human docs: <https://docs.setto.io> · OpenAPI: <https://docs.setto.io/openapi.json>
- Any docs page as raw Markdown: append `.md`, e.g.
  <https://docs.setto.io/docs/concepts/populate.md>. Index: `/llms.txt`.
