Skip to main content
Documentation

Public competitions API

Two read-only JSON endpoints for community tools: list the competitions that are open or in progress, then fetch one competition's fighter and map pool. They need no API key, no account and no headers, so a plain HTTP GET works from anywhere, including a Tabletop Simulator mod.

EndpointReturns
GET /api/v1/public/competitionsCompetitions open for entry or in progress
GET /api/v1/public/competitions/{id}/poolThat competition's fighters and maps

Base URL: https://www.tabletopleague.com. The machine-readable contract is in the interactive reference under Public.

List competitions

curl "https://www.tabletopleague.com/api/v1/public/competitions?game=unmatched"
ParamDescription
gameOptional. A game slug, such as unmatched. Omit it for every game. An unknown slug returns 404.
organizationOptional. An organization slug, the part after /o/ in its page address. Lists only that organizer's competitions.
statusOptional. published (open for registration) or active (in progress). Omit it for both.

The three params combine, and a competition has to match all of them.

{
  "success": true,
  "data": {
    "competitions": [
      {
        "id": "3f1c2b9e-7d4a-4c1e-9b2f-5a6d7e8f9012",
        "name": "Szeged Gambit V. Forduló",
        "slug": "szeged-gambit-v-fordulo",
        "status": "ACTIVE",
        "type": "LADDER",
        "format": "TOURNAMENT",
        "start_date": "2026-10-01T00:00:00+00:00",
        "end_date": null,
        "url": "https://www.tabletopleague.com/o/szeged-gambit/szeged-gambit-v-fordulo",
        "organization": { "name": "Szeged Gambit", "slug": "szeged-gambit" },
        "game": { "name": "Unmatched", "slug": "unmatched" }
      }
    ]
  },
  "meta": { "request_id": "…", "timestamp": "…" },
  "_meta": { "request_id": "…", "timestamp": "…", "terms_url": "/api-terms" }
}

One organizer's competitions

To list only the official events, pass the organizer's slug. Unmatched's official events are all run by one organization, unmatched-tournament:

curl "https://www.tabletopleague.com/api/v1/public/competitions?organization=unmatched-tournament"

Add &status=published for only the events still open for registration. The slug is not case-sensitive.

  • An organization slug that does not exist returns 404 (NOT_FOUND), the same as an unknown game.
  • A public organization that has nothing open or in progress returns 200 with "competitions": []. An organization that isn't publicly visible returns 404, the same as one that doesn't exist. Treat an empty list as "nothing to show right now", not as an error.

name is the competition's display title exactly as the organizer wrote it, accents included. Competitions in progress (ACTIVE) come first, then those open for registration (PUBLISHED), each group by start date. The list holds at most 200.

Get a competition's pool

curl "https://www.tabletopleague.com/api/v1/public/competitions/3f1c2b9e-7d4a-4c1e-9b2f-5a6d7e8f9012/pool"
{
  "success": true,
  "data": {
    "competition": {
      "id": "3f1c2b9e-7d4a-4c1e-9b2f-5a6d7e8f9012",
      "name": "Szeged Gambit V. Forduló"
    },
    "fighters": [
      { "id": "…", "name": "Bigfoot", "slug": "bigfoot", "children": [] },
      {
        "id": "…",
        "name": "Yennefer & Triss",
        "slug": "yennefer-triss",
        "children": [
          { "id": "…", "name": "Triss", "slug": "triss" },
          { "id": "…", "name": "Yennefer", "slug": "yennefer" }
        ]
      }
    ],
    "maps": [
      { "id": "…", "name": "Sherwood Forest", "slug": "sherwood-forest" }
    ]
  }
}
  • fighters are what a player picks in this competition. Fighters the organizer restricted, and retired ones that are no longer legal here, are left out.
  • children lists the playable characters of a composite pick: a player picks "Yennefer & Triss" and plays Yennefer and Triss. It is empty for an ordinary fighter. Match your objects on either level.
  • maps can be empty, for a competition without a map pool or a game without maps.
  • Both lists are sorted by name. The id values are stable and are the same ids the site uses everywhere else.

Which competitions you can see

  • The list shows exactly what the public competitions page shows. Draft, unlisted, members-only and deleted competitions never appear, and neither do friendlies or play groups.
  • The pool works for any competition you have the id of, as long as it is not a draft, not members-only and not deleted. That includes unlisted competitions and finished ones. Anything else returns 404, with no hint of which case it was.

Errors, rate limit and caching

Errors use the same envelope with success: false and an error.code: VALIDATION_ERROR (400, a bad status, game, organization or id), NOT_FOUND (404), RATE_LIMITED (429) or INTERNAL_ERROR (500).

  • Rate limit: 60 requests per minute per IP address, for each endpoint. Beyond it you get 429 with X-RateLimit-* headers.
  • Caching: successful responses are cached for up to 5 minutes. A new competition or a status change (opening registration, starting, finishing) shows up in the list within about 5 minutes. A fighter or map rename shows up in the pool within about 5 to 10 minutes. Errors are never cached.

Example: Tabletop Simulator (Lua)

Fetch the list, then the pool of the competition a player picked:

local BASE = "https://www.tabletopleague.com/api/v1/public/competitions"

function loadCompetitions()
  WebRequest.get(BASE .. "?game=unmatched", function(request)
    if request.is_error then
      print("Request failed: " .. request.error)
      return
    end
    local body = JSON.decode(request.text)
    for _, competition in ipairs(body.data.competitions) do
      print(competition.status .. "  " .. competition.name .. "  " .. competition.id)
    end
  end)
end

function loadPool(competitionId)
  WebRequest.get(BASE .. "/" .. competitionId .. "/pool", function(request)
    if request.is_error then return end
    local pool = JSON.decode(request.text).data
    for _, fighter in ipairs(pool.fighters) do
      print("Fighter: " .. fighter.name)
      for _, child in ipairs(fighter.children) do
        print("  plays as: " .. child.name)
      end
    end
    for _, map in ipairs(pool.maps) do
      print("Map: " .. map.name)
    end
  end)
end

Stability

These endpoints only grow.

New fields may be added to any object. No field will be renamed, retyped or removed without a new /v2 path, so ignore fields you do not recognise. See the API Terms for acceptable use.