Developers
Run tournaments from your code or your AI agent: brackets, round robin, Swiss and groups, for any game or sport. Free, rate-limited.
MCP server
Add Kontest to Claude, ChatGPT, Cursor or any MCP client and ask it to “set up a round robin for my 12 colleagues” or “report 3–1 for Ada in the semi-final”. The server is at https://kontest.games/mcp (Streamable HTTP). Your client signs you in with Kontest the first time.
Claude Code:
claude mcp add --transport http kontest https://kontest.games/mcpAny client that takes a JSON config:
{
"mcpServers": {
"kontest": { "type": "http", "url": "https://kontest.games/mcp" }
}
}A client that can’t sign in can send an API key instead, as Authorization: Bearer kt_live_….
Tools: whoami, list_tournaments, get_tournament, update_tournament, add_entrants, remove_entrant, seed_tournament, start_tournament, report_result, clear_result, cancel_tournament, and list_webhooks, create_webhook, update_webhook, delete_webhook, test_webhook, list_webhook_deliveries.
REST API
JSON over HTTPS at https://kontest.games/api/v1, described in full by OpenAPI 3.1. Reading public tournaments needs no key.
# Read a tournament
curl https://kontest.games/api/v1/tournaments/office-fifa-cup-x7k2Every tournament also reads as Markdown at https://kontest.games/t/<slug>.md. Errors are { "error": { "code", "message" } }.
Webhooks
Have Kontest tell your server when your tournaments change: a match is ready or finished, somebody joins, the tournament completes. Add one below, or POST /api/v1/webhooks. Each covers every tournament you organise.
Events:
tournament.startedThe draw is fixed and the first matches can be played.tournament.completedThe last result counts: final places and the champion.tournament.cancelledThe organiser called it off.tournament.resetBack to sign-up from running, results gone.entrant.addedSomebody joined, or the organiser added a name.entrant.removedAn entrant was taken out before the start.entrant.checked_inAn entrant checked in, or out (checkedIn says which).match.readyBoth sides of a match are known: it can be played.match.reportedA player reported a result; it waits for the other side.match.disputedThe other side said otherwise; the organiser decides.match.completedA result counts: confirmed, the organiser’s, or left alone 24 hours.match.clearedThe organiser cleared a result.
Each is a POST with a JSON body:
{
"type": "match.completed",
"timestamp": "2026-10-12T18:04:05.000Z",
"data": {
"tournament": { "id": "trn_…", "slug": "office-fifa-cup-x7k2", "name": "Office FIFA Cup",
"game": "FIFA 26", "state": "running", "url": "https://kontest.games/t/office-fifa-cup-x7k2" },
"match": { "id": "W1-1", "round": 1, "a": { "id": "ten_…", "name": "Ada" },
"b": { "id": "ten_…", "name": "Bo" }, "state": "done", "winner": "a",
"scoreA": 3, "scoreB": 1, "forfeit": false }
}
}They follow Standard Webhooks: check the webhook-signature header with your endpoint’s secret, using any of its libraries:
import { Webhook } from 'standardwebhooks'
const wh = new Webhook(process.env.KONTEST_WEBHOOK_SECRET) // whsec_…
const event = wh.verify(rawBody, request.headers) // throws if not from KontestAnswer with any 2xx within 10 seconds. Anything else is tried again after 5 seconds, 5 minutes, 30 minutes, then 2, 5, 10 and 10 hours. The webhook-id header stays the same on every try, so ignore one you’ve already had. An endpoint that does nothing but fail for 5 days is turned off. Order isn’t guaranteed: go by the timestamp, or read the tournament.
Signing in
API keys are for your own scripts and servers: made below, shown once, revocable. They act as you.
OAuth 2.1 is for apps and agents acting for other people: the authorization code flow with PKCE (S256), dynamic client registration, and refresh tokens that rotate. Discovery: /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource. Scopes: tournaments:write and webhooks:write.
Organising needs a kept account, not a guest one. Limits are per account; a 429 says when to try again.