{"openapi":"3.1.0","info":{"title":"Kicksmash API","version":"1.0.0","summary":"Kicksmash is the open, agent-native way to organise padel: create a match, share one link, and let people or their assistants join, all through an API that anyone may use.","description":"Reads need no key. Writes work without a key with a daily allowance per address; a free key (POST /api/v1/keys, issued instantly to people and assistants alike) raises it and unlocks webhooks. Public data is CC BY 4.0; the code is Apache-2.0. An MCP server with the same capabilities lives at /mcp.","license":{"name":"CC BY 4.0 (data), Apache-2.0 (code)","url":"https://github.com/evhg/padel-matchup/blob/main/LICENSE"},"contact":{"url":"https://kicksma.sh/developers"}},"servers":[{"url":"https://kicksma.sh"}],"externalDocs":{"description":"Developer guide, MCP server and agent charter","url":"https://kicksma.sh/developers"},"tags":[{"name":"matches","description":"Create, read and join matches and tournaments"},{"name":"venues","description":"Public venue boards"},{"name":"clubs","description":"Club pages: booking links, free courts, founding clubs"},{"name":"groups","description":"Crews that play together"},{"name":"schedules","description":"Americano rotations, no data stored"},{"name":"keys","description":"Optional keys for roomier limits and webhooks"},{"name":"webhooks","description":"Signed callbacks on match events"}],"paths":{"/api/v1/matches/{code}":{"get":{"tags":["matches"],"operationId":"getMatch","summary":"A match by its 4-character code","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","minLength":4,"maxLength":4}}],"responses":{"200":{"description":"The match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Match"}}}},"404":{"description":"No such match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/matches":{"post":{"tags":["matches"],"operationId":"createMatch","summary":"Create a match or an americano tournament","description":"Returns the public match plus the organizer's personal token, personal link and private manage link. Give those to the person you act for; never publish them.","security":[{},{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMatch"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMatchResult"}}}},"422":{"description":"Invalid request; the message names the field","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited; the hint says how to get more room","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/matches/{code}/join":{"post":{"tags":["matches"],"operationId":"joinMatch","summary":"Join a match by name (or by an existing personal token)","description":"Outcomes: joined, waitlisted, already_in, full, or requested when the player's level is outside the match's range (the organizer approves on the match page).","security":[{},{"bearer":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinMatch"}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinMatchResult"}}}},"404":{"description":"No such match","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid request; the message names the field","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited; the hint says how to get more room","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/boards/{slug}":{"get":{"tags":["venues"],"operationId":"getBoard","summary":"Open, organizer-listed matches at a venue","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"Lower-case venue name with dashes, e.g. padel-indoor-bcn"}],"responses":{"200":{"description":"The board","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Board"}}}},"404":{"description":"Unknown venue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/clubs":{"get":{"tags":["clubs"],"operationId":"listClubs","summary":"Live club pages, optionally for one city","parameters":[{"name":"city","in":"query","schema":{"type":"string"},"description":"phuket or singapore"}],"responses":{"200":{"description":"Clubs","content":{"application/json":{"schema":{"type":"object","properties":{"city":{"type":["string","null"]},"clubs":{"type":"array","items":{"$ref":"#/components/schemas/Club"}},"cities":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}}}}}}}}}}}},"/api/v1/clubs/{slug}":{"get":{"tags":["clubs"],"operationId":"getClub","summary":"A club page: booking link, courts, today's free courts when the club shares a feed","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The club","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Club"}}}},"404":{"description":"No live club page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/groups/{code}":{"get":{"tags":["groups"],"operationId":"getGroup","summary":"A group: members, weekly slot, upcoming matches","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","minLength":6,"maxLength":6}}],"responses":{"200":{"description":"The group","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Group"}}}},"404":{"description":"No such group","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/schedule":{"get":{"tags":["schedules"],"operationId":"getSchedule","summary":"An americano rotation (exact when the field is in fours)","parameters":[{"name":"players","in":"query","schema":{"type":"integer","minimum":4,"maximum":64}},{"name":"names","in":"query","description":"Comma-separated names; overrides players","schema":{"type":"string"}},{"name":"courts","in":"query","schema":{"type":"integer","minimum":1,"maximum":16}},{"name":"rounds","in":"query","schema":{"type":"integer","minimum":1,"maximum":40}},{"name":"seed","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"The schedule","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Schedule"}}}},"422":{"description":"Invalid request; the message names the field","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited; the hint says how to get more room","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["schedules"],"operationId":"postSchedule","summary":"Same as GET, with a JSON body","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleInput"}}}},"responses":{"200":{"description":"The schedule","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Schedule"}}}},"422":{"description":"Invalid request; the message names the field","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited; the hint says how to get more room","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/keys":{"post":{"tags":["keys"],"operationId":"createKey","summary":"Get an API key instantly","description":"No approval, no email verification. The key is shown once. Say who or what will use it; an assistant may name itself.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":80},"email":{"type":"string","format":"email"},"agent":{"type":"string","maxLength":80,"description":"e.g. \"claude\", \"chatgpt\", \"my-club-bot\""}},"required":["name"]}}}},"responses":{"201":{"description":"The key, once","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"prefix":{"type":"string"},"name":{"type":"string"},"limits":{"type":"object"},"next":{"type":"string"}}}}}},"422":{"description":"Invalid request; the message names the field","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited; the hint says how to get more room","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks":{"get":{"tags":["webhooks"],"operationId":"listWebhooks","summary":"Your webhooks","security":[{"bearer":[]}],"responses":{"200":{"description":"Webhooks","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}},"401":{"description":"Key required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["webhooks"],"operationId":"createWebhook","summary":"Subscribe to match events","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["match.created","match.updated","match.joined","match.left","match.full","match.cancelled","match.result"]}},"filter":{"type":"object","properties":{"venueSlug":{"type":"string"},"groupCode":{"type":"string"},"codes":{"type":"array","items":{"type":"string"}}}}},"required":["url"]}}}},"responses":{"201":{"description":"Created; the secret is shown once","content":{"application/json":{"schema":{"type":"object","properties":{"webhook":{"$ref":"#/components/schemas/Webhook"},"secret":{"type":"string"},"signing":{"type":"string"}}}}}},"401":{"description":"Key required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid request; the message names the field","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited; the hint says how to get more room","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks/{id}":{"delete":{"tags":["webhooks"],"operationId":"deleteWebhook","summary":"Remove a webhook","security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Removed"},"404":{"description":"Not yours or already gone","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"Optional for writes, required for webhooks. Keys start with ks_live_."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"hint":{"type":"string"},"status":{"type":"integer"},"docs":{"type":"string"}},"required":["code","message","status"]}},"required":["error"]},"Match":{"type":"object","description":"Exactly what the public match page shows. First names and levels only; never emails, phones, tokens or manage links.","properties":{"code":{"type":"string"},"url":{"type":"string"},"type":{"type":"string","enum":["match","tournament"]},"title":{"type":["string","null"]},"status":{"type":"string","enum":["open","full","cancelled","past"]},"startsAt":{"type":"string","format":"date-time"},"endsAt":{"type":"string","format":"date-time"},"tz":{"type":"string"},"venue":{"type":["object","null"],"properties":{"name":{"type":"string"},"slug":{"type":["string","null"]},"mapUrl":{"type":["string","null"]},"court":{"type":["string","null"]},"boardUrl":{"type":["string","null"]}}},"capacity":{"type":"integer"},"players":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"level":{"type":["number","null"]},"organizer":{"type":"boolean"},"status":{"type":"string","enum":["joined","confirmed","invited"]}}}},"spotsLeft":{"type":"integer"},"waitlist":{"type":"integer"},"whenFull":{"type":"string","enum":["waitlist","closed"]},"level":{"type":["object","null"],"properties":{"min":{"type":["number","null"]},"max":{"type":["number","null"]},"preset":{"type":["string","null"],"description":"bronze, silver, gold, platinum or custom"}}},"group":{"type":["object","null"],"properties":{"code":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}}},"listed":{"type":"boolean"},"bookingUrl":{"type":["string","null"]},"cost":{"type":["string","null"],"description":"What each player pays, free text."},"note":{"type":["string","null"]},"result":{"type":["object","null"],"properties":{"sets":{"type":"array","items":{"type":"object","properties":{"a":{"type":"integer"},"b":{"type":"integer"}}}},"teamA":{"type":"array","items":{"type":"string"}},"teamB":{"type":"array","items":{"type":"string"}},"winner":{"type":"string","enum":["a","b","draw"],"description":"Sets can be empty when only the winner was recorded."},"confirmed":{"type":"boolean"}}},"createdAt":{"type":"string","format":"date-time"}},"required":["code","url","type","status","startsAt","tz","capacity","players","spotsLeft"]},"CreateMatch":{"type":"object","properties":{"type":{"default":"match","description":"A match is exactly four players. A tournament is an americano with 4 to 64 players in fours.","type":"string","enum":["match","tournament"]},"startsAt":{"type":"string","minLength":10,"maxLength":40,"description":"ISO 8601 date-time. With an offset or Z it is absolute; without one it is read in tz. Example: 2026-09-11T19:00"},"tz":{"type":"string","minLength":1,"maxLength":64,"description":"IANA time zone the players live in, for example Asia/Singapore or Asia/Bangkok."},"venue":{"description":"Club or court name. Enables the venue board and the booking link.","type":"string","maxLength":80},"venueMapUrl":{"type":"string","maxLength":500,"format":"uri"},"court":{"description":"Court within the venue, e.g. \"3\".","type":"string","maxLength":40},"capacity":{"description":"Tournaments only, a multiple of 4. Matches are always 4.","type":"integer","minimum":4,"maximum":64},"format":{"description":"Tournaments only. americano: partners rotate, everyone plays everyone. mexicano: courts by standings after round 1. king: winners move up a court, losers down.","type":"string","enum":["americano","mexicano","king"]},"pointsPerMatch":{"description":"Tournaments only: fixed points per match (16, 21, 24, 32). Omit for free scoring; mexicano defaults to 24.","type":"integer","minimum":4,"maximum":99},"whenFull":{"default":"waitlist","type":"string","enum":["waitlist","closed"]},"levelMin":{"description":"Level range 0 to 7 (Playtomic-style). Omit both for any level.","anyOf":[{"type":"number","minimum":0,"maximum":7},{"type":"null"}]},"levelMax":{"anyOf":[{"type":"number","minimum":0,"maximum":7},{"type":"null"}]},"title":{"type":"string","maxLength":80},"note":{"type":"string","maxLength":500},"bookingUrl":{"description":"The club's booking page or confirmation link, shown to players.","type":"string","maxLength":500,"format":"uri"},"cost":{"description":"What each player pays, as free text: 400 THB, €8, split 4 ways.","type":"string","maxLength":40},"listOnVenueBoard":{"default":false,"description":"Show the match on the public venue board (/v/{venue-slug}). Off by default.","type":"boolean"},"organizer":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":40,"description":"First name is enough."},"token":{"description":"Personal token of an existing Kicksmash player, returned by earlier calls, so the same person organizes again.","type":"string","minLength":8,"maxLength":64},"email":{"description":"Optional. Gets the organizer link and calendar invite by email.","type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"level":{"type":"number","minimum":0,"maximum":7}},"required":["name"]},"organizerPlays":{"default":true,"description":"Seat the organizer in the match (default). False when they only organize.","type":"boolean"}},"required":["startsAt","tz","organizer"]},"JoinMatch":{"type":"object","properties":{"code":{"type":"string","minLength":4,"maxLength":4,"description":"The 4-character match code from the link, e.g. kicksma.sh/AB12 → AB12."},"name":{"description":"Required unless token is given.","type":"string","minLength":1,"maxLength":40},"token":{"description":"Personal token of an existing player.","type":"string","minLength":8,"maxLength":64},"email":{"description":"Optional. Sends a calendar invite that updates itself.","type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"level":{"description":"Needed once when the match has a level range and the player has no level yet.","type":"number","minimum":0,"maximum":7}},"required":["code"]},"ScheduleInput":{"type":"object","properties":{"players":{"description":"Number of players; ignored when names are given.","type":"integer","minimum":4,"maximum":64},"names":{"minItems":4,"maxItems":64,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"courts":{"description":"Defaults to floor(players / 4).","type":"integer","minimum":1,"maximum":16},"rounds":{"description":"Defaults to players − 1 when the field is in fours (every pair partners once), else players.","type":"integer","minimum":1,"maximum":40},"format":{"default":"americano","type":"string","enum":["americano"]},"seed":{"description":"Same seed, same schedule.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}}},"CreateMatchResult":{"type":"object","properties":{"match":{"$ref":"#/components/schemas/Match"},"organizer":{"type":"object","properties":{"name":{"type":"string"},"personalToken":{"type":"string"},"personalUrl":{"type":"string"},"manageUrl":{"type":"string"}}},"shareUrl":{"type":"string"},"next":{"type":"string"}}},"JoinMatchResult":{"type":"object","properties":{"outcome":{"type":"string","enum":["joined","waitlisted","already_in","full","requested"]},"match":{"$ref":"#/components/schemas/Match"},"player":{"type":"object","properties":{"name":{"type":"string"},"personalToken":{"type":"string"},"personalUrl":{"type":"string"}}},"next":{"type":"string"}}},"Club":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"city":{"type":["string","null"]},"mapUrl":{"type":["string","null"]},"website":{"type":["string","null"]},"booking":{"type":["object","null"],"properties":{"url":{"type":"string"},"platform":{"type":["string","null"],"description":"playtomic, matchi, playbypoint, … or null for the club's own page"},"platformName":{"type":["string","null"]}}},"courts":{"type":["integer","null"]},"about":{"type":["string","null"]},"founding":{"type":"boolean"},"freeCourts":{"type":["object","null"],"description":"Today's free court-hours from the club's own feed; null when the club shares none","properties":{"day":{"type":"string"},"tz":{"type":"string"},"fetchedAt":{"type":"string"},"slots":{"type":"array","items":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"},"free":{"type":"integer"}}}}}},"boardUrl":{"type":"string"},"rankingUrl":{"type":"string"},"calendarUrl":{"type":"string"}}},"Board":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"mapUrl":{"type":["string","null"]},"calendarUrl":{"type":"string"},"matches":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"url":{"type":"string"},"type":{"type":"string"},"title":{"type":["string","null"]},"startsAt":{"type":"string"},"tz":{"type":"string"},"capacity":{"type":"integer"},"players":{"type":"integer"},"spotsLeft":{"type":"integer"},"level":{"type":["object","null"],"properties":{"min":{"type":["number","null"]},"max":{"type":["number","null"]},"preset":{"type":["string","null"],"description":"bronze, silver, gold, platinum or custom"}}}}}}}},"Group":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"calendarUrl":{"type":"string"},"venue":{"type":"object"},"tz":{"type":"string"},"type":{"type":"string"},"capacity":{"type":"integer"},"level":{"type":["object","null"],"properties":{"min":{"type":["number","null"]},"max":{"type":["number","null"]},"preset":{"type":["string","null"],"description":"bronze, silver, gold, platinum or custom"}}},"weekly":{"type":["object","null"],"properties":{"weekday":{"type":"integer","description":"0 = Sunday"},"time":{"type":"string"},"leadDays":{"type":"integer"}}},"members":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"level":{"type":["number","null"]},"admin":{"type":"boolean"}}}},"upcoming":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"url":{"type":"string"},"startsAt":{"type":"string"},"title":{"type":["string","null"]}}}}}},"Schedule":{"type":"object","properties":{"format":{"type":"string"},"players":{"type":"integer"},"courts":{"type":"integer"},"exact":{"type":"boolean"},"rounds":{"type":"array","items":{"type":"object","properties":{"round":{"type":"integer"},"matches":{"type":"array","items":{"type":"object","properties":{"court":{"type":"integer"},"a":{"type":"array","items":{"type":"string"}},"b":{"type":"array","items":{"type":"string"}}}}},"resting":{"type":"array","items":{"type":"string"}}}}},"note":{"type":"string"}}},"Webhook":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"filter":{"type":["object","null"]},"createdAt":{"type":"string"}}}}}}