# RoundsWon API

The RoundsWon tournament API: create tournaments, add players, start stages and send match results from your own code, a bot or a game server, with API keys and idempotent requests.

The RoundsWon API lets your own code run tournaments: a Discord bot that adds players, a game server that sends results the moment a match ends, or your own site's admin panel. Anything a host can do on a tournament page, the API can do.

Every change to a tournament is an **event**, like `entry.added` or `match.result`. Your code sends events, and [webhooks](https://roundswon.com/docs/webhooks) tell your code about every event that happens, whoever sent it.

For read only widgets on a website you do not need the API at all. See [Embed a bracket on any website](https://roundswon.com/docs/embed-widgets).

## Base address and keys

```
https://api.freeaffiliate.co/api/tournaments/v1
```

Make a key on your [developer page](https://roundswon.com/developer). Send it on every request:

```
Authorization: Bearer rw_live_...
```

A key acts as you, on the tournaments you host or help run as an admin or scorer. Keep it secret like a password. Keys are shown once when made; delete one any time to cut it off.

## Endpoints

| Method and path | What it does |
| --- | --- |
| `GET /tournaments` | The tournaments you host or run as staff, newest first |
| `POST /tournaments` | Creates a tournament (same fields as the create page, with `agree: true`) |
| `GET /tournaments/{code}` | A tournament's settings, its full state (entries, stages, matches, places, payouts) and `seq` |
| `GET /tournaments/{code}/events?after={seq}&limit={n}` | The tournament's events in order, from after `seq`, up to 200 at a time |
| `POST /tournaments/{code}/events` | Sends an event: `{ "type", "refs", "data" }` |

## Create a tournament

```bash
curl -X POST https://api.freeaffiliate.co/api/tournaments/v1/tournaments \
  -H "Authorization: Bearer rw_live_..." \
  -H "Content-Type: application/json" \
  -d '{"agree":true,"name":"Friday Night Doubles","game":"rocket-league","stages":[{"format":"single","settings":{"bestOf":3}}]}'
```

`agree: true` confirms that entry is free and that you pay any prize yourself. The answer has the new tournament's `slug`, its code.

## Send an event

```bash
curl -X POST https://api.freeaffiliate.co/api/tournaments/v1/tournaments/abc123/events \
  -H "Authorization: Bearer rw_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: match-4821" \
  -d '{"type":"match.result","refs":{"stage":0,"match":"r1-0"},"data":{"scores":[2,1]}}'
```

The answer is the event as stored, plus everything it set off (`consequences`), like the next match becoming ready or the tournament finishing:

```json
{
  "event": { "id": "evt_...", "seq": 14, "type": "match.result", "refs": { "stage": 0, "match": "r1-0" }, "data": { "scores": [2, 1] }, "match": { "status": "done" } },
  "consequences": [{ "type": "match.ready", "refs": { "stage": 0, "match": "r2-0" } }]
}
```

`refs` say what the event is about: `stage` (0 for the first), `match` (its key, see [match keys](https://roundswon.com/docs/bracket-maker)) and `entry` (an entry's id from the state).

## Events your code can send

| Type | refs | data |
| --- | --- | --- |
| `entry.added` | | `{ "name" }` a player or team by name |
| `entry.updated` | `entry` | `{ "seed" }` |
| `entry.removed` | `entry` | before the start |
| `entry.checked_in` | `entry` | |
| `entry.disqualified` | `entry` | |
| `stage.started` | `stage: 0` | starts the tournament, the draw is shuffled fairly |
| `stage.finished` | `stage` | ends a stage early, like a leaderboard when time is up |
| `match.result` | `stage`, `match` | `{ "scores": [a, b] }` head to head, `{ "sides": [{ "placement", "kills" }] }` a lobby, `{ "score" }` a leaderboard score |
| `score.rejected` | `stage`, `match` | turns down a leaderboard score |
| `payout.sent`, `payout.unsent` | `entry` | marks a prize as paid, or takes that back |
| `tournament.cancelled` | | |
| `tournament.verified` | | confirms the final results |

Sending `match.result` again replaces the result, as long as the matches it feeds have not been played. A player entering with their account and reporting their own score happen on the tournament page, where RoundsWon knows who they are.

## Idempotency

Send an `Idempotency-Key` header with any value you choose, like your own match id. Sending the same request with the same key again returns the event it already made instead of making a second one, so retries after a timeout are always safe.

## Errors

Errors answer with a status code and a plain sentence that says what to fix:

```json
{ "error": "In a best of 3 the winner has 2 games." }
```

| Status | Meaning |
| --- | --- |
| 400 | Something in the request is wrong, the message says what |
| 401 | The key is missing, wrong or deleted |
| 404 | No such tournament, or your key's account does not run it |
| 409 | The event does not fit right now, like a result for a match nobody is in yet |
| 429 | Too many requests: up to 300 a minute per account |

## Staying in step

Every event has a `seq`, its place in the tournament's list: 1, 2, 3, with no gaps. Keep the last `seq` you handled. If your code was offline, ask for `GET /tournaments/{code}/events?after={seq}` and you have everything you missed, in order.
