{"openapi":"3.1.0","info":{"title":"Akıllı Futbol API","version":"1.0.0","description":"The API behind the Akıllı Futbol apps. Sign up or sign in at /api/auth (see the Auth tag), keep the signed token from the `set-auth-token` response header and send it as `Authorization: Bearer <token>`. Under /api/v1, successful responses are `{ \"data\": … }` and errors are `{ \"error\": { \"code\", \"message\", \"fields\" } }`, an unknown path included (404 NOT_FOUND) and server errors (500 INTERNAL); only a method a path does not support gets an empty 405. The /api/auth endpoints belong to Better Auth: their answers have no `data` envelope and their errors are `{ \"code\", \"message\" }`."},"servers":[{"url":"https://akilli.futbol"}],"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer"},"cookie":{"type":"apiKey","in":"cookie","name":"better-auth.session_token"},"venueCookie":{"type":"apiKey","in":"cookie","name":"akillifutbol-venue.session_token","description":"The pitch admin panel's own session (web only), set by POST /api/venue-auth/sign-in/username for a venue login"}}},"paths":{"/api/auth/sign-up/username":{"post":{"operationId":"postAuthSignUpUsername","summary":"Create an account and sign in","description":"The signed session token is in the `set-auth-token` response header: keep it and send it as `Authorization: Bearer <token>`.\n\nRate-limited per IP: 20 per 10 minutes (429).\n\n400 INVALID_SIGN_UP with an error code per field in `errors`: `displayName`: displayName; `username`: username (3–20 characters of a–z, 0–9 and _); `password`: password (12–128 characters), passwordIsUsername or passwordCommon; `adult`, `privacyNotice`, `terms`: required; `form`: generic (positions or locale not valid).\n\n400 USERNAME_IS_ALREADY_TAKEN with `errors.username` = usernameTaken (reserved names count as taken).\n\n400 VALIDATION_ERROR when a field is missing or has the wrong type. 403 for a sign-up a browser sends from another site.","tags":["Auth"],"security":[],"x-envelope":false,"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"displayName":{"type":"string","description":"2–40 characters"},"username":{"type":"string","description":"3–20: a-z, 0-9, _ (stored lower-case)"},"password":{"type":"string","description":"12–128 characters; not a common password, not built from the username or name"},"positions":{"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]}},"adult":{"type":"boolean","const":true},"privacyNotice":{"type":"boolean","const":true},"terms":{"type":"boolean","const":true},"locale":{"type":"string","enum":["tr","en"]}},"required":["displayName","username","password","adult","privacyNotice","terms","locale"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"username":{"type":"string"}},"required":["id","username"]}},"required":["user"],"description":"The signed session token is in the `set-auth-token` response header: keep it and send it as `Authorization: Bearer <token>`."}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"e.g. INVALID_USERNAME_OR_PASSWORD; missing on the per-IP 429"},"message":{"type":"string"},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Sign-up only: an error code per field"}},"required":["message"]}}}}}}},"/api/auth/sign-in/username":{"post":{"operationId":"postAuthSignInUsername","summary":"Sign in","description":"The signed session token is in the `set-auth-token` response header: keep it and send it as `Authorization: Bearer <token>`.\n\n401 INVALID_USERNAME_OR_PASSWORD for any wrong name or password; 403 BANNED_USER; 429 TOO_MANY_ATTEMPTS after repeated wrong passwords for the username, from any address, or 429 without a code past the per-IP limit (3 per 10 seconds).","tags":["Auth"],"security":[],"x-envelope":false,"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"},"rememberMe":{"type":"boolean"}},"required":["username","password"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"redirect":{"type":"boolean","description":"Always false"},"token":{"type":"string","description":"The raw session token. It is refused as a bearer token: use the set-auth-token header. The signed session token is in the `set-auth-token` response header: keep it and send it as `Authorization: Bearer <token>`."},"user":{"type":"object","properties":{"id":{"type":"string"},"username":{"type":"string","description":"Lower-case; the profile address, /p/{username}"},"displayUsername":{"type":"string"},"name":{"type":"string","description":"The display name"},"email":{"type":"string","description":"Not an address: a random placeholder Better Auth needs (accounts have no email). Ignore it"},"emailVerified":{"type":"boolean"},"image":{"type":["string","null"]},"role":{"description":"\"admin\" or \"user\"","type":["string","null"]},"banned":{"type":["boolean","null"]},"banReason":{"type":["string","null"]},"banExpires":{"type":["string","null"]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","username","displayUsername","name","email","emailVerified","image","role","banned","banReason","banExpires","createdAt","updatedAt"]}},"required":["redirect","token","user"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"e.g. INVALID_USERNAME_OR_PASSWORD; missing on the per-IP 429"},"message":{"type":"string"},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Sign-up only: an error code per field"}},"required":["message"]}}}}}}},"/api/auth/is-username-available":{"post":{"operationId":"postAuthIsUsernameAvailable","summary":"Check whether a username is free","description":"What the sign-up form asks as you type. Reserved names (staff words, the brand, app routes such as destek or developers) and names in use answer `available: false`, as sign-up would refuse them. 422 USERNAME_TOO_SHORT, USERNAME_TOO_LONG or INVALID_USERNAME (3–20 characters of a–z, 0–9 and _). Rate-limited per IP: 30 per minute (429).","tags":["Auth"],"security":[],"x-envelope":false,"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"username":{"type":"string","description":"As typed: it is checked after the same normalising as sign-up (Turkish letters folded, lower-cased)"}},"required":["username"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"available":{"type":"boolean"}},"required":["available"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"e.g. INVALID_USERNAME_OR_PASSWORD; missing on the per-IP 429"},"message":{"type":"string"},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Sign-up only: an error code per field"}},"required":["message"]}}}}}}},"/api/auth/sign-out":{"post":{"operationId":"postAuthSignOut","summary":"Sign out (ends this session)","description":"The session ends only when this answers 200 `{ success: true }`; after any other answer the token may still work. Forget the stored token either way.","tags":["Auth"],"security":[{"bearer":[]},{"cookie":[]}],"x-envelope":false,"requestBody":{"required":false,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{},"description":"Nothing to send: leave the body out, or send {}"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"e.g. INVALID_USERNAME_OR_PASSWORD; missing on the per-IP 429"},"message":{"type":"string"},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Sign-up only: an error code per field"}},"required":["message"]}}}}}}},"/api/auth/get-session":{"get":{"operationId":"getAuthGetSession","summary":"The current session and user","description":"Answers 200 with `null`, not 401, when there is no valid session.","tags":["Auth"],"security":[{"bearer":[]},{"cookie":[]}],"x-envelope":false,"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"session":{"type":"object","properties":{"id":{"type":"string"},"userId":{"type":"string"},"token":{"type":"string","description":"The raw session token. It is refused as a bearer token: use the set-auth-token header. The signed session token is in the `set-auth-token` response header: keep it and send it as `Authorization: Bearer <token>`."},"expiresAt":{"type":"string","description":"ISO 8601. A session lasts 7 days and is extended, at most once a day, while it is used"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"ipAddress":{"type":["string","null"]},"userAgent":{"type":["string","null"]},"impersonatedBy":{"type":["string","null"]}},"required":["id","userId","token","expiresAt","createdAt","updatedAt","ipAddress","userAgent","impersonatedBy"]},"user":{"type":"object","properties":{"id":{"type":"string"},"username":{"type":"string","description":"Lower-case; the profile address, /p/{username}"},"displayUsername":{"type":"string"},"name":{"type":"string","description":"The display name"},"email":{"type":"string","description":"Not an address: a random placeholder Better Auth needs (accounts have no email). Ignore it"},"emailVerified":{"type":"boolean"},"image":{"type":["string","null"]},"role":{"description":"\"admin\" or \"user\"","type":["string","null"]},"banned":{"type":["boolean","null"]},"banReason":{"type":["string","null"]},"banExpires":{"type":["string","null"]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","username","displayUsername","name","email","emailVerified","image","role","banned","banReason","banExpires","createdAt","updatedAt"]}},"required":["session","user"]},{"type":"null"}],"description":"null when the token or cookie is missing, expired or signed out"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"e.g. INVALID_USERNAME_OR_PASSWORD; missing on the per-IP 429"},"message":{"type":"string"},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Sign-up only: an error code per field"}},"required":["message"]}}}}}}},"/api/auth/change-password":{"post":{"operationId":"postAuthChangePassword","summary":"Change the password","description":"Signs every session out, this one included, and starts a new one: replace the stored token with the new `set-auth-token` response header (a browser gets a new cookie). Every other device has to sign in again. A `revokeOtherSessions` field is ignored.\n\n400 INVALID_PASSWORD (the current password is wrong), PASSWORD_TOO_SHORT, PASSWORD_TOO_LONG, PASSWORD_IS_USERNAME or PASSWORD_TOO_COMMON (a common word or a password built from the username or name); 401 without a valid session.","tags":["Auth"],"security":[{"bearer":[]},{"cookie":[]}],"x-envelope":false,"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"currentPassword":{"type":"string"},"newPassword":{"type":"string","description":"12–128 characters; not the username, nothing common or built from the username or name"}},"required":["currentPassword","newPassword"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"token":{"type":"string","description":"The raw session token. It is refused as a bearer token: use the set-auth-token header. The signed session token is in the `set-auth-token` response header: keep it and send it as `Authorization: Bearer <token>`."},"user":{"type":"object","properties":{"id":{"type":"string"},"username":{"type":"string","description":"Lower-case; the profile address, /p/{username}"},"displayUsername":{"type":"string"},"name":{"type":"string","description":"The display name"},"email":{"type":"string","description":"Not an address: a random placeholder Better Auth needs (accounts have no email). Ignore it"},"emailVerified":{"type":"boolean"},"image":{"type":["string","null"]},"role":{"description":"\"admin\" or \"user\"","type":["string","null"]},"banned":{"type":["boolean","null"]},"banReason":{"type":["string","null"]},"banExpires":{"type":["string","null"]},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}},"required":["id","username","displayUsername","name","email","emailVerified","image","role","banned","banReason","banExpires","createdAt","updatedAt"]}},"required":["token","user"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"e.g. INVALID_USERNAME_OR_PASSWORD; missing on the per-IP 429"},"message":{"type":"string"},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Sign-up only: an error code per field"}},"required":["message"]}}}}}}},"/api/v1/me":{"get":{"operationId":"getMe","summary":"Your profile settings","tags":["Me"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"The player id"},"userId":{"type":"string"},"username":{"type":"string","description":"Fixed: the profile address, /p/{username}"},"handle":{"type":"string"},"displayName":{"type":"string"},"positions":{"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]}},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}],"description":"Your profile photo (PUT /api/v1/me/photo), or null"},"visibility":{"type":"string","enum":["public","teammates","private"],"description":"Who sees your stats, teams and match history. public: everyone; teammates: people who share a team with you or a match you took part in; private: nobody else"},"role":{"description":"\"admin\" or \"user\"","type":["string","null"]},"activeSessions":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Signed-in devices, this one included"}},"required":["id","userId","username","handle","displayName","positions","photo","visibility","role","activeSessions"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}},"patch":{"operationId":"patchMe","summary":"Change your display name, positions or visibility","tags":["Me"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"displayName":{"type":"string","minLength":1},"positions":{"maxItems":16,"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]}},"visibility":{"type":"string","enum":["public","teammates","private"]}},"description":"Send only what changes. displayName: 2–40 characters with no invisible characters or staff titles, as at sign-up (also the account name); positions: replaces the list."}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","description":"The player id"},"userId":{"type":"string"},"username":{"type":"string","description":"Fixed: the profile address, /p/{username}"},"handle":{"type":"string"},"displayName":{"type":"string"},"positions":{"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]}},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}],"description":"Your profile photo (PUT /api/v1/me/photo), or null"},"visibility":{"type":"string","enum":["public","teammates","private"],"description":"Who sees your stats, teams and match history. public: everyone; teammates: people who share a team with you or a match you took part in; private: nobody else"},"role":{"description":"\"admin\" or \"user\"","type":["string","null"]},"activeSessions":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Signed-in devices, this one included"}},"required":["id","userId","username","handle","displayName","positions","photo","visibility","role","activeSessions"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"422 INVALID with `fields.displayName` = required | tooShort | tooLong | invalid, or `fields.username` = immutable when a different username is sent (the username never changes). Change the password at /api/auth/change-password; sign out other devices at /api/auth/revoke-other-sessions."}},"/api/auth/revoke-other-sessions":{"post":{"operationId":"postAuthRevokeOtherSessions","summary":"Sign out every other device","description":"This device stays signed in. The other sessions end only when this answers 200 `{ status: true }`; after any other answer they may still work, so try again.","tags":["Auth"],"security":[{"bearer":[]},{"cookie":[]}],"x-envelope":false,"requestBody":{"required":false,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{},"description":"Nothing to send: leave the body out, or send {}"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"boolean"}},"required":["status"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"e.g. INVALID_USERNAME_OR_PASSWORD; missing on the per-IP 429"},"message":{"type":"string"},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Sign-up only: an error code per field"}},"required":["message"]}}}}}}},"/api/v1/teams":{"get":{"operationId":"getTeams","summary":"Teams the caller plays for (archived teams left out)","tags":["Teams"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"slug":{"type":"string","description":"URL slug, e.g. kartallar-7k2m"},"name":{"type":"string"},"city":{"type":["string","null"]},"kitHomeHex":{"type":"string","pattern":"^#[0-9a-f]{6}$","description":"#rrggbb, lower-case"},"kitAwayHex":{"anyOf":[{"type":"string","pattern":"^#[0-9a-f]{6}$","description":"#rrggbb, lower-case"},{"type":"null"}]},"crest":{"anyOf":[{"type":"object","properties":{"shape":{"type":"string","enum":["shield","round","rounded","diamond","hexagon","pennant","arched","oval","crest","octagon","ticket","star"]},"primary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"secondary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"letter":{"type":"string"},"pattern":{"default":"plain","type":"string","enum":["plain","split","stripes","sash","chevron","hoops"]},"detail":{"default":"none","type":"string","enum":["none","star","ball","crown","laurel","wings"]}},"required":["shape","primary","secondary","letter"]},{"type":"null"}],"description":"Custom crest, or null to use the team name and kit colours"},"role":{"type":"string","enum":["captain","co_captain","member"],"description":"The caller's role in the team"},"memberCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","slug","name","city","kitHomeHex","kitAwayHex","crest","role","memberCount"]}}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}},"post":{"operationId":"postTeams","summary":"Start a team. The caller becomes captain and a 6-letter invite code is issued. Name and city are stored NFKC-normalised with single spaces. An optional crest has a shape, two distinct #rrggbb colours, one Unicode letter, a pattern (default plain) and a detail (default none). Field errors: name required/tooShort/tooLong/invalid (hidden or text-direction characters)/staffWord (reads as staff: “Akıllı Futbol”, “Admin”, “Destek”…), city tooLong/invalid, kitHomeHex required/invalid, kitAwayHex invalid/sameAsHome, crest.shape/primary/secondary/letter/pattern/detail invalid (equal colours: crest.secondary=differentColour).","tags":["Teams"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"minLength":2,"maxLength":40,"type":"string"},"city":{"anyOf":[{"maxLength":60,"type":"string"},{"type":"null"}]},"kitHomeHex":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"kitAwayHex":{"anyOf":[{"type":"string","pattern":"^#[0-9a-f]{6}$"},{"type":"null"}]},"crest":{"anyOf":[{"type":"object","properties":{"shape":{"type":"string","enum":["shield","round","rounded","diamond","hexagon","pennant","arched","oval","crest","octagon","ticket","star"]},"primary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"secondary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"letter":{"type":"string"},"pattern":{"default":"plain","type":"string","enum":["plain","split","stripes","sash","chevron","hoops"]},"detail":{"default":"none","type":"string","enum":["none","star","ball","crown","laurel","wings"]}},"required":["shape","primary","secondary","letter"]},{"type":"null"}]}},"required":["name","kitHomeHex"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"slug":{"type":"string"},"name":{"type":"string"},"city":{"type":["string","null"]},"kitHomeHex":{"type":"string","pattern":"^#[0-9a-f]{6}$","description":"#rrggbb, lower-case"},"kitAwayHex":{"anyOf":[{"type":"string","pattern":"^#[0-9a-f]{6}$","description":"#rrggbb, lower-case"},{"type":"null"}]},"crest":{"anyOf":[{"type":"object","properties":{"shape":{"type":"string","enum":["shield","round","rounded","diamond","hexagon","pennant","arched","oval","crest","octagon","ticket","star"]},"primary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"secondary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"letter":{"type":"string"},"pattern":{"default":"plain","type":"string","enum":["plain","split","stripes","sash","chevron","hoops"]},"detail":{"default":"none","type":"string","enum":["none","star","ball","crown","laurel","wings"]}},"required":["shape","primary","secondary","letter"]},{"type":"null"}]},"archivedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"memberCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"viewer":{"type":"object","properties":{"role":{"anyOf":[{"type":"string","enum":["captain","co_captain","member"]},{"type":"null"}],"description":"Null for an admin who is not a member"},"canEdit":{"type":"boolean"},"canRotateCode":{"type":"boolean"},"canArchive":{"type":"boolean"},"mustHandOver":{"type":"boolean","description":"The caller is the last captain: leaving needs a new captain first"},"onlyMember":{"type":"boolean","description":"Leaving would archive the team"}},"required":["role","canEdit","canRotateCode","canArchive","mustHandOver","onlyMember"]},"code":{"description":"The live 6-letter invite code; null once archived","type":["string","null"]},"inviteUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"The link to share (a short /j/<code> link that opens the join page)"},"members":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"positions":{"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]},"description":"Follows the member's visibility, as on their profile: empty when it is private, unless it is your own or you are an admin"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}],"description":"Follows the member's visibility, like the positions"},"role":{"type":"string","enum":["captain","co_captain","member"]},"number":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"joinedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"isViewer":{"type":"boolean"},"can":{"type":"object","properties":{"roles":{"type":"array","items":{"type":"string","enum":["captain","co_captain","member"]},"description":"Roles the caller may give this member"},"remove":{"type":"boolean"}},"required":["roles","remove"]}},"required":["playerId","displayName","handle","kind","positions","photo","role","number","joinedAt","isViewer","can"]},"description":"Captains first, then co-captains, then players"},"matches":{"type":"object","properties":{"upcoming":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"status":{"type":"string","enum":["planned","played","cancelled"]},"title":{"type":["string","null"]},"venue":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"verified":{"type":"boolean"}},"required":["name","verified"]},{"type":"null"}]},"sides":{"type":"array","items":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"ours":{"type":"boolean","description":"This team plays on this side"}},"required":["side","label","goals","ours"]}}},"required":["id","startsAt","status","title","venue","sides"]}},"recent":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"status":{"type":"string","enum":["planned","played","cancelled"]},"title":{"type":["string","null"]},"venue":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"verified":{"type":"boolean"}},"required":["name","verified"]},{"type":"null"}]},"sides":{"type":"array","items":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"ours":{"type":"boolean","description":"This team plays on this side"}},"required":["side","label","goals","ours"]}}},"required":["id","startsAt","status","title","venue","sides"]}}},"required":["upcoming","recent"],"description":"Up to 5 each"}},"required":["id","slug","name","city","kitHomeHex","kitAwayHex","crest","archivedAt","createdAt","memberCount","viewer","code","inviteUrl","members","matches"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/teams/{slug}":{"get":{"operationId":"getTeamsBySlug","summary":"A team: roster, the caller's permissions, the invite code and matches. 404 unless the caller is a member or an admin.","tags":["Teams"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"slug":{"type":"string"},"name":{"type":"string"},"city":{"type":["string","null"]},"kitHomeHex":{"type":"string","pattern":"^#[0-9a-f]{6}$","description":"#rrggbb, lower-case"},"kitAwayHex":{"anyOf":[{"type":"string","pattern":"^#[0-9a-f]{6}$","description":"#rrggbb, lower-case"},{"type":"null"}]},"crest":{"anyOf":[{"type":"object","properties":{"shape":{"type":"string","enum":["shield","round","rounded","diamond","hexagon","pennant","arched","oval","crest","octagon","ticket","star"]},"primary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"secondary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"letter":{"type":"string"},"pattern":{"default":"plain","type":"string","enum":["plain","split","stripes","sash","chevron","hoops"]},"detail":{"default":"none","type":"string","enum":["none","star","ball","crown","laurel","wings"]}},"required":["shape","primary","secondary","letter"]},{"type":"null"}]},"archivedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"memberCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"viewer":{"type":"object","properties":{"role":{"anyOf":[{"type":"string","enum":["captain","co_captain","member"]},{"type":"null"}],"description":"Null for an admin who is not a member"},"canEdit":{"type":"boolean"},"canRotateCode":{"type":"boolean"},"canArchive":{"type":"boolean"},"mustHandOver":{"type":"boolean","description":"The caller is the last captain: leaving needs a new captain first"},"onlyMember":{"type":"boolean","description":"Leaving would archive the team"}},"required":["role","canEdit","canRotateCode","canArchive","mustHandOver","onlyMember"]},"code":{"description":"The live 6-letter invite code; null once archived","type":["string","null"]},"inviteUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"The link to share (a short /j/<code> link that opens the join page)"},"members":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"positions":{"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]},"description":"Follows the member's visibility, as on their profile: empty when it is private, unless it is your own or you are an admin"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}],"description":"Follows the member's visibility, like the positions"},"role":{"type":"string","enum":["captain","co_captain","member"]},"number":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"joinedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"isViewer":{"type":"boolean"},"can":{"type":"object","properties":{"roles":{"type":"array","items":{"type":"string","enum":["captain","co_captain","member"]},"description":"Roles the caller may give this member"},"remove":{"type":"boolean"}},"required":["roles","remove"]}},"required":["playerId","displayName","handle","kind","positions","photo","role","number","joinedAt","isViewer","can"]},"description":"Captains first, then co-captains, then players"},"matches":{"type":"object","properties":{"upcoming":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"status":{"type":"string","enum":["planned","played","cancelled"]},"title":{"type":["string","null"]},"venue":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"verified":{"type":"boolean"}},"required":["name","verified"]},{"type":"null"}]},"sides":{"type":"array","items":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"ours":{"type":"boolean","description":"This team plays on this side"}},"required":["side","label","goals","ours"]}}},"required":["id","startsAt","status","title","venue","sides"]}},"recent":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"status":{"type":"string","enum":["planned","played","cancelled"]},"title":{"type":["string","null"]},"venue":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"verified":{"type":"boolean"}},"required":["name","verified"]},{"type":"null"}]},"sides":{"type":"array","items":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"ours":{"type":"boolean","description":"This team plays on this side"}},"required":["side","label","goals","ours"]}}},"required":["id","startsAt","status","title","venue","sides"]}}},"required":["upcoming","recent"],"description":"Up to 5 each"}},"required":["id","slug","name","city","kitHomeHex","kitAwayHex","crest","archivedAt","createdAt","memberCount","viewer","code","inviteUrl","members","matches"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}},"patch":{"operationId":"patchTeamsBySlug","summary":"Edit name, city, kits and crest (captains and co-captains). Only the top-level fields sent change; send the whole crest to replace it. Null clears city, kitAwayHex or crest. A crest has a shape, two distinct #rrggbb colours, one Unicode letter, a pattern (default plain) and a detail (default none). Crest errors use crest.shape/primary/secondary/letter/pattern/detail; equal colours give crest.secondary=differentColour. The slug never changes.","tags":["Teams"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"minLength":2,"maxLength":40,"type":"string"},"city":{"anyOf":[{"maxLength":60,"type":"string"},{"type":"null"}]},"kitHomeHex":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"kitAwayHex":{"anyOf":[{"type":"string","pattern":"^#[0-9a-f]{6}$"},{"type":"null"}]},"crest":{"anyOf":[{"type":"object","properties":{"shape":{"type":"string","enum":["shield","round","rounded","diamond","hexagon","pennant","arched","oval","crest","octagon","ticket","star"]},"primary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"secondary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"letter":{"type":"string"},"pattern":{"default":"plain","type":"string","enum":["plain","split","stripes","sash","chevron","hoops"]},"detail":{"default":"none","type":"string","enum":["none","star","ball","crown","laurel","wings"]}},"required":["shape","primary","secondary","letter"]},{"type":"null"}]}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"slug":{"type":"string"},"name":{"type":"string"},"city":{"type":["string","null"]},"kitHomeHex":{"type":"string","pattern":"^#[0-9a-f]{6}$","description":"#rrggbb, lower-case"},"kitAwayHex":{"anyOf":[{"type":"string","pattern":"^#[0-9a-f]{6}$","description":"#rrggbb, lower-case"},{"type":"null"}]},"crest":{"anyOf":[{"type":"object","properties":{"shape":{"type":"string","enum":["shield","round","rounded","diamond","hexagon","pennant","arched","oval","crest","octagon","ticket","star"]},"primary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"secondary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"letter":{"type":"string"},"pattern":{"default":"plain","type":"string","enum":["plain","split","stripes","sash","chevron","hoops"]},"detail":{"default":"none","type":"string","enum":["none","star","ball","crown","laurel","wings"]}},"required":["shape","primary","secondary","letter"]},{"type":"null"}]},"archivedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"memberCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"viewer":{"type":"object","properties":{"role":{"anyOf":[{"type":"string","enum":["captain","co_captain","member"]},{"type":"null"}],"description":"Null for an admin who is not a member"},"canEdit":{"type":"boolean"},"canRotateCode":{"type":"boolean"},"canArchive":{"type":"boolean"},"mustHandOver":{"type":"boolean","description":"The caller is the last captain: leaving needs a new captain first"},"onlyMember":{"type":"boolean","description":"Leaving would archive the team"}},"required":["role","canEdit","canRotateCode","canArchive","mustHandOver","onlyMember"]},"code":{"description":"The live 6-letter invite code; null once archived","type":["string","null"]},"inviteUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"The link to share (a short /j/<code> link that opens the join page)"},"members":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"positions":{"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]},"description":"Follows the member's visibility, as on their profile: empty when it is private, unless it is your own or you are an admin"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}],"description":"Follows the member's visibility, like the positions"},"role":{"type":"string","enum":["captain","co_captain","member"]},"number":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"joinedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"isViewer":{"type":"boolean"},"can":{"type":"object","properties":{"roles":{"type":"array","items":{"type":"string","enum":["captain","co_captain","member"]},"description":"Roles the caller may give this member"},"remove":{"type":"boolean"}},"required":["roles","remove"]}},"required":["playerId","displayName","handle","kind","positions","photo","role","number","joinedAt","isViewer","can"]},"description":"Captains first, then co-captains, then players"},"matches":{"type":"object","properties":{"upcoming":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"status":{"type":"string","enum":["planned","played","cancelled"]},"title":{"type":["string","null"]},"venue":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"verified":{"type":"boolean"}},"required":["name","verified"]},{"type":"null"}]},"sides":{"type":"array","items":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"ours":{"type":"boolean","description":"This team plays on this side"}},"required":["side","label","goals","ours"]}}},"required":["id","startsAt","status","title","venue","sides"]}},"recent":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"status":{"type":"string","enum":["planned","played","cancelled"]},"title":{"type":["string","null"]},"venue":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"verified":{"type":"boolean"}},"required":["name","verified"]},{"type":"null"}]},"sides":{"type":"array","items":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"ours":{"type":"boolean","description":"This team plays on this side"}},"required":["side","label","goals","ours"]}}},"required":["id","startsAt","status","title","venue","sides"]}}},"required":["upcoming","recent"],"description":"Up to 5 each"}},"required":["id","slug","name","city","kitHomeHex","kitAwayHex","crest","archivedAt","createdAt","memberCount","viewer","code","inviteUrl","members","matches"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"403 fields.form=managersOnly for members who are not captains or co-captains; 409 fields.form=archived once the team is archived."}},"/api/v1/teams/{slug}/code":{"post":{"operationId":"postTeamsBySlugCode","summary":"Replace the invite code (captains and co-captains); the old code stops working at once","tags":["Teams"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"code":{"type":"string"},"inviteUrl":{"type":"string","format":"uri"}},"required":["code","inviteUrl"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"403 fields.form=managersOnly for members who are not captains or co-captains; 409 fields.form=archived once the team is archived."}},"/api/v1/teams/{slug}/members/{playerId}":{"patch":{"operationId":"patchTeamsBySlugMembersByPlayerId","summary":"Change a member's role. Captains and co-captains manage players and co-captains; only captains make or change captains (403 fields.form=captainOnly). The last captain cannot step down (409 fields.form=lastCaptain). A player who is not (or no longer) on the team: 404 fields.form=memberGone.","tags":["Teams"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"playerId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"role":{"type":"string","enum":["captain","co_captain","member"]}},"required":["role"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"playerId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"role":{"type":"string","enum":["captain","co_captain","member"]}},"required":["playerId","role"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"403 fields.form=managersOnly for members who are not captains or co-captains; 409 fields.form=archived once the team is archived."},"delete":{"operationId":"deleteTeamsBySlugMembersByPlayerId","summary":"Remove a member (same rules as roles; 404 fields.form=memberGone when they are not on the team). Removing yourself is leaving.","tags":["Teams"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"playerId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"archived":{"type":"boolean"}},"required":["archived"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"403 fields.form=managersOnly for members who are not captains or co-captains; 409 fields.form=archived once the team is archived."}},"/api/v1/teams/{slug}/leave":{"post":{"operationId":"postTeamsBySlugLeave","summary":"Leave the team. The last captain must make someone else captain first (409 fields.form=handOver); the last member leaving archives the team.","tags":["Teams"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"archived":{"type":"boolean","description":"The team is archived after this"}},"required":["archived"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/teams/{slug}/archive":{"post":{"operationId":"postTeamsBySlugArchive","summary":"Archive the team (captains only): it leaves everyone's list and its code stops working; matches and stats stay","tags":["Teams"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"archivedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["archivedAt"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"403 fields.form=captainOnly for co-captains and players."}},"/api/v1/join/{code}":{"get":{"operationId":"getJoinByCode","summary":"What a six-letter code opens: a team or a match","tags":["Invites"],"security":[],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"kind":{"type":"string","const":"team"},"code":{"type":"string","description":"The canonical code: six capital letters"},"team":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"city":{"type":["string","null"]},"kitHomeHex":{"type":"string","description":"#rrggbb"},"kitAwayHex":{"type":["string","null"]},"memberCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"captain":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"handle":{"type":["string","null"]},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["id","displayName","handle","photo"]},{"type":"null"}],"description":"Null when signed out"},"archived":{"type":"boolean","description":"Closed teams take no new members"}},"required":["id","slug","name","city","kitHomeHex","kitAwayHex","memberCount","captain","archived"]},"viewer":{"type":"object","properties":{"signedIn":{"type":"boolean"},"member":{"type":"boolean"},"role":{"anyOf":[{"type":"string","enum":["captain","co_captain","member"]},{"type":"null"}]},"canJoin":{"type":"boolean"}},"required":["signedIn","member","role","canJoin"]}},"required":["kind","code","team","viewer"]},{"type":"object","properties":{"kind":{"type":"string","const":"match"},"code":{"type":"string"},"match":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["fixture","pickup"]},"status":{"type":"string","enum":["planned","played","cancelled"]},"state":{"type":"string","enum":["open","cancelled","finished"],"description":"Answers are taken while open: not cancelled, not played and no score in yet. Kick-off does not close a match, so people can still join one they played."},"title":{"type":["string","null"]},"startsAt":{"type":"string","description":"ISO 8601"},"durationMin":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"venue":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean"}},"required":["name","city","district","address","verified"]},{"type":"null"}]},"pitch":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"indoor":{"type":"boolean"}},"required":["name","indoor"]},{"type":"null"}]},"sides":{"type":"array","items":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"team":{"anyOf":[{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"}},"required":["slug","name"]},{"type":"null"}]}},"required":["side","label","goals","team"]}},"coming":{"type":"object","properties":{"A":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"B":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"none":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"maybe":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["A","B","none","total","maybe"],"description":"Yes answers per side (none: no side yet) and the number of maybes"},"players":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"rsvp":{"type":"string","enum":["yes","maybe"]},"you":{"type":"boolean"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["id","displayName","side","rsvp","you","photo"]},"description":"Who said yes or maybe; empty when signed out"},"organizer":{"anyOf":[{"type":"object","properties":{"displayName":{"type":"string"}},"required":["displayName"]},{"type":"null"}],"description":"Null when signed out"}},"required":["id","kind","status","state","title","startsAt","durationMin","venue","pitch","sides","coming","players","organizer"]},"viewer":{"type":"object","properties":{"signedIn":{"type":"boolean"},"rsvp":{"anyOf":[{"type":"string","enum":["invited","yes","maybe","no"]},{"type":"null"}]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"canAnswer":{"type":"boolean"}},"required":["signedIn","rsvp","side","canAnswer"]}},"required":["kind","code","match","viewer"]}]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Codes are case-insensitive; dashes and spaces are ignored. Signed out, the preview has counts but no names. The code a match had when it was cancelled, and the code a team had when it was closed, keep opening the preview (`state: cancelled`, `archived: true`) so the link explains itself; codes replaced before that are `revoked`. 404 NOT_FOUND with `fields.code`: `invalid` (not six code letters), `unknown` or `revoked` (replaced by a newer code). 429 RATE_LIMITED after 20 wrong codes in 15 minutes (per account; signed out, per address, or in one count shared by every caller whose address cannot be trusted); malformed codes are not counted."},"post":{"operationId":"postJoinByCode","summary":"Use a code: join the team, or answer the match invitation","tags":["Invites"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"rsvp":{"description":"Match codes only; defaults to yes. Ignored for team codes.","type":"string","enum":["yes","maybe","no"]}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"kind":{"type":"string","const":"team"},"team":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"}},"required":["id","slug","name"]},"role":{"type":"string","enum":["captain","co_captain","member"]},"alreadyMember":{"type":"boolean"}},"required":["kind","team","role","alreadyMember"]},{"type":"object","properties":{"kind":{"type":"string","const":"match"},"match":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"participant":{"type":"object","properties":{"rsvp":{"type":"string","enum":["invited","yes","maybe","no"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side of the person's team, when they are in one"},"respondedAt":{"type":["string","null"]}},"required":["rsvp","side","respondedAt"]}},"required":["kind","match","participant"]}]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"A team code makes you a member (again is a no-op: `alreadyMember`); closed teams answer 409 with `fields.team = archived`. A match code records your answer and puts you on your team's side; cancelled matches and matches with a score answer 409 with `fields.match = cancelled | finished`. 404 NOT_FOUND with `fields.code`: `invalid` (not six code letters), `unknown` or `revoked` (replaced by a newer code). 429 RATE_LIMITED after 20 wrong codes in 15 minutes (per account; signed out, per address, or in one count shared by every caller whose address cannot be trusted); malformed codes are not counted."}},"/api/v1/matches":{"get":{"operationId":"getMatches","summary":"My matches: upcoming (soonest first) and past (latest first)","tags":["Matches"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"upcoming":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":["string","null"]},"kind":{"type":"string","enum":["pickup","fixture"]},"status":{"type":"string","enum":["planned","played","cancelled"]},"startsAt":{"type":"string","description":"ISO 8601 (UTC)"},"durationMin":{"type":"number"},"scoreState":{"type":"string","enum":["none","entered","confirmed"]},"venue":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"district":{"type":["string","null"]},"city":{"type":["string","null"]},"verified":{"type":"boolean"}},"required":["name","district","city","verified"]},{"type":"null"}]},"pitch":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]},{"type":"null"}]},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]},"bibHex":{"type":["string","null"]},"teamId":{"type":["string","null"]},"teamName":{"type":["string","null"]}},"required":["side","label","goals","bibHex","teamId","teamName"]},"B":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]},"bibHex":{"type":["string","null"]},"teamId":{"type":["string","null"]},"teamName":{"type":["string","null"]}},"required":["side","label","goals","bibHex","teamId","teamName"]}},"required":["A","B"]},"coming":{"type":"number","description":"How many said \"yes\""},"me":{"type":"object","properties":{"rsvp":{"anyOf":[{"type":"string","enum":["invited","yes","maybe","no"]},{"type":"null"}]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"isOrganizer":{"type":"boolean"}},"required":["rsvp","side","isOrganizer"]},"canRespond":{"type":"boolean"}},"required":["id","title","kind","status","startsAt","durationMin","scoreState","venue","pitch","sides","coming","me","canRespond"]}},"past":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":["string","null"]},"kind":{"type":"string","enum":["pickup","fixture"]},"status":{"type":"string","enum":["planned","played","cancelled"]},"startsAt":{"type":"string","description":"ISO 8601 (UTC)"},"durationMin":{"type":"number"},"scoreState":{"type":"string","enum":["none","entered","confirmed"]},"venue":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"district":{"type":["string","null"]},"city":{"type":["string","null"]},"verified":{"type":"boolean"}},"required":["name","district","city","verified"]},{"type":"null"}]},"pitch":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]},{"type":"null"}]},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]},"bibHex":{"type":["string","null"]},"teamId":{"type":["string","null"]},"teamName":{"type":["string","null"]}},"required":["side","label","goals","bibHex","teamId","teamName"]},"B":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]},"bibHex":{"type":["string","null"]},"teamId":{"type":["string","null"]},"teamName":{"type":["string","null"]}},"required":["side","label","goals","bibHex","teamId","teamName"]}},"required":["A","B"]},"coming":{"type":"number","description":"How many said \"yes\""},"me":{"type":"object","properties":{"rsvp":{"anyOf":[{"type":"string","enum":["invited","yes","maybe","no"]},{"type":"null"}]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"isOrganizer":{"type":"boolean"}},"required":["rsvp","side","isOrganizer"]},"canRespond":{"type":"boolean"}},"required":["id","title","kind","status","startsAt","durationMin","scoreState","venue","pitch","sides","coming","me","canRespond"]}}},"required":["upcoming","past"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"At most 60 upcoming matches and the 40 latest past ones, with no further pages yet."},"post":{"operationId":"postMatches","summary":"Create a match","tags":["Matches"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"title":{"description":"Up to 60 characters","type":["string","null"]},"kind":{"description":"pickup (default): sides picked among friends; fixture: two teams","type":"string","enum":["pickup","fixture"]},"date":{"description":"Kick-off date on an Istanbul clock, YYYY-MM-DD (with `time`)","type":"string"},"time":{"description":"Kick-off on an Istanbul clock, HH:MM (with `date`)","type":"string"},"startsAt":{"description":"Instead of date + time: an ISO 8601 instant with an offset","type":"string"},"durationMin":{"description":"Default 60","anyOf":[{"type":"number","const":60},{"type":"number","const":90},{"type":"number","const":120}]},"venueId":{"type":["string","null"]},"pitchId":{"description":"A pitch of the venue (sets the venue when venueId is left out)","type":["string","null"]},"recording":{"description":"official only on a pitch with an official camera","type":"string","enum":["upload","official"]},"sides":{"description":"Default labels \"Turuncular\" and \"Maviler\"","type":"object","properties":{"A":{"type":"object","properties":{"label":{"type":"string","description":"1–30 characters, e.g. \"Turuncular\""},"teamId":{"description":"One of your teams playing this side; its members are invited on this side","type":["string","null"]},"bibHex":{"description":"The bib colour as #rrggbb, or null for no bibs","type":["string","null"]}},"required":["label"]},"B":{"type":"object","properties":{"label":{"type":"string","description":"1–30 characters, e.g. \"Turuncular\""},"teamId":{"description":"One of your teams playing this side; its members are invited on this side","type":["string","null"]},"bibHex":{"description":"The bib colour as #rrggbb, or null for no bibs","type":["string","null"]}},"required":["label"]}},"required":["A","B"]},"invitedTeamIds":{"description":"Your teams whose members all get an invitation","type":"array","items":{"type":"string"}},"notes":{"description":"Up to 500 characters","type":["string","null"]},"reservationId":{"description":"One of your confirmed bookings (GET /api/v1/reservations) that has no match yet: the match takes its venue, pitch, start and length and is linked to it (ADR-005)","type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"code":{"type":"string"},"inviteUrl":{"type":"string","format":"uri","description":"The link to share for the code, as for teams: a short /j/<code> link that opens the join page."}},"required":["id","code","inviteUrl"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"You come (\"yes\") on side A, or on B when you belong to side B's team only. Teams playing a side and invited teams get every member invited, on their team's side. 422 `fields` codes: `required`, `invalid` (also hidden or text-direction characters in the title, a side's label or the notes), `staffWord` (the title or a side's label reads as staff: “Akıllı Futbol”, “Admin”, “Destek”…), `tooLong`, `notMember` (a team you are not in), `sameTeam`, `noOfficialCamera`; `reservationId` = `invalid` (not your confirmed booking), `unavailable` (it has a match, or its time passed) or `mismatch` (the venue, pitch, start or length you sent differ). 429 `fields.form = tooManyMatches`: 20 new matches a day per person."}},"/api/v1/matches/{id}":{"get":{"operationId":"getMatchesById","summary":"A match with its sides, people, place and code","tags":["Matches"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"title":{"type":["string","null"]},"kind":{"type":"string","enum":["pickup","fixture"]},"status":{"type":"string","enum":["planned","played","cancelled"]},"startsAt":{"type":"string"},"endsAt":{"type":"string"},"durationMin":{"type":"number"},"recording":{"type":"string","enum":["upload","official"]},"scoreState":{"type":"string","enum":["none","entered","confirmed"]},"notes":{"type":["string","null"]},"cancelledAt":{"type":["string","null"]},"organizer":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"type":["string","null"]}},"required":["playerId","displayName","handle"]},"venue":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean"},"slug":{"description":"The venue's public booking page is /venues/<slug> while its pitch admin login is on; null otherwise","type":["string","null"]}},"required":["id","name","city","district","address","verified","slug"]},{"type":"null"}]},"pitch":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"indoor":{"type":"boolean"},"officialCamera":{"type":"boolean"}},"required":["id","name","indoor","officialCamera"]},{"type":"null"}]},"camera":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"official":{"type":"boolean"}},"required":["id","label","official"]},{"type":"null"}],"description":"Set when the match waits for the pitch's official camera"},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]},"bibHex":{"type":["string","null"]},"teamId":{"type":["string","null"]},"teamName":{"type":["string","null"]}},"required":["side","label","goals","bibHex","teamId","teamName"]},"B":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]},"bibHex":{"type":["string","null"]},"teamId":{"type":["string","null"]},"teamName":{"type":["string","null"]}},"required":["side","label","goals","bibHex","teamId","teamName"]}},"required":["A","B"]},"participants":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"rsvp":{"type":"string","enum":["invited","yes","maybe","no"]},"respondedAt":{"type":["string","null"]},"attended":{"type":"boolean"},"isOrganizer":{"type":"boolean"},"positions":{"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]},"description":"Where the person likes to play, as on their profile; empty when they gave none or their visibility hides it from you, and for guests"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["playerId","displayName","handle","kind","side","rsvp","respondedAt","attended","isOrganizer","positions","photo"]}},"teams":{"type":"array","items":{"type":"object","properties":{"teamId":{"type":"string"},"name":{"type":"string"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"members":{"type":"number"}},"required":["teamId","name","side","members"]}},"code":{"description":"The live 6-letter join code; null once the match is cancelled","type":["string","null"]},"inviteUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"The link to share for the code, as for teams: a short /j/<code> link that opens the join page. Null with the code"},"hasVideo":{"type":"boolean"},"kickedOff":{"type":"boolean"},"ended":{"type":"boolean","description":"Kick-off plus the duration has passed"},"viewer":{"type":"object","properties":{"playerId":{"type":"string","description":"Your player id, to find yourself in participants"},"canManage":{"type":"boolean"},"isOrganizer":{"type":"boolean"},"isAdmin":{"type":"boolean"},"rsvp":{"anyOf":[{"type":"string","enum":["invited","yes","maybe","no"]},{"type":"null"}]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"canRespond":{"type":"boolean"}},"required":["playerId","canManage","isOrganizer","isAdmin","rsvp","side","canRespond"]}},"required":["id","title","kind","status","startsAt","endsAt","durationMin","recording","scoreState","notes","cancelledAt","organizer","venue","pitch","camera","sides","participants","teams","code","inviteUrl","hasVideo","kickedOff","ended","viewer"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}},"patch":{"operationId":"patchMatchesById","summary":"Change a match (managers)","tags":["Matches"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"title":{"description":"Up to 60 characters","type":["string","null"]},"kind":{"description":"pickup (default): sides picked among friends; fixture: two teams","type":"string","enum":["pickup","fixture"]},"date":{"description":"Kick-off date on an Istanbul clock, YYYY-MM-DD (with `time`)","type":"string"},"time":{"description":"Kick-off on an Istanbul clock, HH:MM (with `date`)","type":"string"},"startsAt":{"description":"Instead of date + time: an ISO 8601 instant with an offset","type":"string"},"durationMin":{"description":"Default 60","anyOf":[{"type":"number","const":60},{"type":"number","const":90},{"type":"number","const":120}]},"venueId":{"type":["string","null"]},"pitchId":{"description":"A pitch of the venue (sets the venue when venueId is left out)","type":["string","null"]},"recording":{"description":"official only on a pitch with an official camera","type":"string","enum":["upload","official"]},"notes":{"description":"Up to 500 characters","type":["string","null"]},"sides":{"description":"Only the fields you send change; a newly set team gets its members invited","type":"object","properties":{"A":{"type":"object","properties":{"label":{"type":"string","description":"1–30 characters, e.g. \"Turuncular\""},"teamId":{"description":"One of your teams playing this side; its members are invited on this side","type":["string","null"]},"bibHex":{"description":"The bib colour as #rrggbb, or null for no bibs","type":["string","null"]}}},"B":{"type":"object","properties":{"label":{"type":"string","description":"1–30 characters, e.g. \"Turuncular\""},"teamId":{"description":"One of your teams playing this side; its members are invited on this side","type":["string","null"]},"bibHex":{"description":"The bib colour as #rrggbb, or null for no bibs","type":["string","null"]}}}}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"title":{"type":["string","null"]},"kind":{"type":"string","enum":["pickup","fixture"]},"status":{"type":"string","enum":["planned","played","cancelled"]},"startsAt":{"type":"string"},"endsAt":{"type":"string"},"durationMin":{"type":"number"},"recording":{"type":"string","enum":["upload","official"]},"scoreState":{"type":"string","enum":["none","entered","confirmed"]},"notes":{"type":["string","null"]},"cancelledAt":{"type":["string","null"]},"organizer":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"type":["string","null"]}},"required":["playerId","displayName","handle"]},"venue":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean"},"slug":{"description":"The venue's public booking page is /venues/<slug> while its pitch admin login is on; null otherwise","type":["string","null"]}},"required":["id","name","city","district","address","verified","slug"]},{"type":"null"}]},"pitch":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"indoor":{"type":"boolean"},"officialCamera":{"type":"boolean"}},"required":["id","name","indoor","officialCamera"]},{"type":"null"}]},"camera":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"official":{"type":"boolean"}},"required":["id","label","official"]},{"type":"null"}],"description":"Set when the match waits for the pitch's official camera"},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]},"bibHex":{"type":["string","null"]},"teamId":{"type":["string","null"]},"teamName":{"type":["string","null"]}},"required":["side","label","goals","bibHex","teamId","teamName"]},"B":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]},"bibHex":{"type":["string","null"]},"teamId":{"type":["string","null"]},"teamName":{"type":["string","null"]}},"required":["side","label","goals","bibHex","teamId","teamName"]}},"required":["A","B"]},"participants":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"rsvp":{"type":"string","enum":["invited","yes","maybe","no"]},"respondedAt":{"type":["string","null"]},"attended":{"type":"boolean"},"isOrganizer":{"type":"boolean"},"positions":{"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]},"description":"Where the person likes to play, as on their profile; empty when they gave none or their visibility hides it from you, and for guests"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["playerId","displayName","handle","kind","side","rsvp","respondedAt","attended","isOrganizer","positions","photo"]}},"teams":{"type":"array","items":{"type":"object","properties":{"teamId":{"type":"string"},"name":{"type":"string"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"members":{"type":"number"}},"required":["teamId","name","side","members"]}},"code":{"description":"The live 6-letter join code; null once the match is cancelled","type":["string","null"]},"inviteUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"The link to share for the code, as for teams: a short /j/<code> link that opens the join page. Null with the code"},"hasVideo":{"type":"boolean"},"kickedOff":{"type":"boolean"},"ended":{"type":"boolean","description":"Kick-off plus the duration has passed"},"viewer":{"type":"object","properties":{"playerId":{"type":"string","description":"Your player id, to find yourself in participants"},"canManage":{"type":"boolean"},"isOrganizer":{"type":"boolean"},"isAdmin":{"type":"boolean"},"rsvp":{"anyOf":[{"type":"string","enum":["invited","yes","maybe","no"]},{"type":"null"}]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"canRespond":{"type":"boolean"}},"required":["playerId","canManage","isOrganizer","isAdmin","rsvp","side","canRespond"]}},"required":["id","title","kind","status","startsAt","endsAt","durationMin","recording","scoreState","notes","cancelledAt","organizer","venue","pitch","camera","sides","participants","teams","code","inviteUrl","hasVideo","kickedOff","ended","viewer"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Answers are kept. A team newly set on a side gets its members invited there, and a team moved to the other side takes its people along. Once the score is in, the details can still be corrected but a side's team cannot change (409 `fields.form = scored`). A match with a booking at its venue moves the booking with it: 409 `fields.form = bookingSlotTaken` when the new time or pitch is not free on the venue's grid, `bookingVenue` when the match would leave the booking's venue (cancel the booking first). Errors: 401 signed out; 404 the match does not exist or you are not part of it; 403 you may see it but not change it (organiser, side captains and admins manage); 409 `fields.form = cancelled` once cancelled; 422 with `fields`."}},"/api/v1/matches/{id}/cancel":{"post":{"operationId":"postMatchesByIdCancel","summary":"Cancel a match (managers)","tags":["Matches"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","const":"cancelled"}},"required":["id","status"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"The code stops working, and the bookings linked to the match that have not started are cancelled with it (their answer reads `@matchCancelled`). 409 `fields.form = scored` once the score is in. Errors: 401 signed out; 404 the match does not exist or you are not part of it; 403 you may see it but not change it (organiser, side captains and admins manage); 409 `fields.form = cancelled` once cancelled; 422 with `fields`."}},"/api/v1/matches/{id}/code":{"post":{"operationId":"postMatchesByIdCode","summary":"Renew the match's join code (managers)","tags":["Matches"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"code":{"type":"string","description":"6 letters A–Z without I and O"},"inviteUrl":{"type":"string","format":"uri","description":"The link to share for the code, as for teams: a short /j/<code> link that opens the join page."}},"required":["code","inviteUrl"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"The old code stops working at once; share the new one again. Errors: 401 signed out; 404 the match does not exist or you are not part of it; 403 you may see it but not change it (organiser, side captains and admins manage); 409 `fields.form = cancelled` once cancelled; 422 with `fields`."}},"/api/v1/matches/{id}/rsvp":{"post":{"operationId":"postMatchesByIdRsvp","summary":"Answer an invitation","tags":["Matches"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"answer":{"type":"string","enum":["yes","maybe","no"]}},"required":["answer"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"rsvp":{"type":"string","enum":["invited","yes","maybe","no"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]}},"required":["rsvp","side"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For invited people and members of invited teams (who join on their team's side). 409 `fields.form` = `cancelled` or `scored`. Errors: 401 signed out; 404 the match does not exist or you are not part of it; 403 you may see it but not change it (organiser, side captains and admins manage); 409 `fields.form = cancelled` once cancelled; 422 with `fields`."}},"/api/v1/matches/{id}/teams":{"post":{"operationId":"postMatchesByIdTeams","summary":"Invite one of your teams (managers)","tags":["Matches"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"teamId":{"type":"string"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]}},"required":["teamId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"invited":{"type":"number"},"members":{"type":"number"}},"required":["invited","members"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Every member not yet in the match is invited. A team playing a side is invited on that side (422 `fields.side = teamPlaysSide` for the other one); otherwise members without a side, or still on the team's earlier side, take `side`. 422 `fields.teamId = notMember` for a team you are not in. Once the score is in, who played and on which side is changed with the score: 409 `fields.form = scored`. Errors: 401 signed out; 404 the match does not exist or you are not part of it; 403 you may see it but not change it (organiser, side captains and admins manage); 409 `fields.form = cancelled` once cancelled; 422 with `fields`."}},"/api/v1/matches/{id}/participants":{"post":{"operationId":"postMatchesByIdParticipants","summary":"Invite a person by username, or add a guest by name (managers)","tags":["Matches"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"username":{"type":"string"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]}},"required":["username"]},{"type":"object","properties":{"guestName":{"type":"string","description":"2–40 characters"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]}},"required":["guestName"]}]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"rsvp":{"type":"string","enum":["invited","yes","maybe","no"]},"respondedAt":{"type":["string","null"]},"attended":{"type":"boolean"},"isOrganizer":{"type":"boolean"},"positions":{"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]},"description":"Where the person likes to play, as on their profile; empty when they gave none or their visibility hides it from you, and for guests"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["playerId","displayName","handle","kind","side","rsvp","respondedAt","attended","isOrganizer","positions","photo"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"A guest is a player without an account, counted as coming; the name follows the same rules as a display name (422 `fields.guestName` = `required`, `tooShort`, `tooLong` or `invalid`). 422 `fields.username = notFound`; 409 `fields.username = already`. Once the score is in, who played and on which side is changed with the score: 409 `fields.form = scored`. Errors: 401 signed out; 404 the match does not exist or you are not part of it; 403 you may see it but not change it (organiser, side captains and admins manage); 409 `fields.form = cancelled` once cancelled; 422 with `fields`."}},"/api/v1/matches/{id}/participants/{playerId}":{"patch":{"operationId":"patchMatchesByIdParticipantsByPlayerId","summary":"Move a player to a side, or answer for a guest (managers)","tags":["Matches"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"playerId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"rsvp":{"description":"Guests only","type":"string","enum":["yes","maybe","no"]}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"playerId":{"type":"string"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"rsvp":{"type":"string","enum":["invited","yes","maybe","no"]}},"required":["playerId","side","rsvp"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"403 when `rsvp` is sent for a registered player (people answer for themselves); 422 `fields.rsvp = invalid` for any other value. Once the score is in, who played and on which side is changed with the score: 409 `fields.form = scored`. Errors: 401 signed out; 404 the match does not exist or you are not part of it; 403 you may see it but not change it (organiser, side captains and admins manage); 409 `fields.form = cancelled` once cancelled; 422 with `fields`."},"delete":{"operationId":"deleteMatchesByIdParticipantsByPlayerId","summary":"Take a player off the match (managers)","tags":["Matches"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"playerId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"playerId":{"type":"string"},"removed":{"type":"boolean","const":true}},"required":["playerId","removed"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"409 `fields.form` = `organizer` (the organiser stays). Once the score is in, who played and on which side is changed with the score: 409 `fields.form = scored`. Errors: 401 signed out; 404 the match does not exist or you are not part of it; 403 you may see it but not change it (organiser, side captains and admins manage); 409 `fields.form = cancelled` once cancelled; 422 with `fields`."}},"/api/v1/venues":{"get":{"operationId":"getVenues","summary":"Search venues","tags":["Venues"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"q","in":"query","required":false,"description":"Words that must all appear in the name, district or city; Turkish letters optional (\"kadikoy\" finds \"Kadıköy\")","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean","description":"Checked by an admin (a partner venue); show the verified mark"},"pitches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"e.g. \"1. Saha\""},"indoor":{"type":"boolean"},"officialCamera":{"type":"boolean","description":"The pitch has the venue's own recorder: a match here can wait for its recording"}},"required":["id","name","indoor","officialCamera"]}}},"required":["id","name","city","district","address","verified","pitches"]}}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Verified venues first, then by name; at most 12. An empty `q` lists venues the same way."},"post":{"operationId":"postVenues","summary":"Add a venue","tags":["Venues"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","description":"2–80 characters"},"city":{"type":"string","description":"2–40 characters"},"district":{"description":"Up to 40 characters","type":["string","null"]},"address":{"description":"Up to 160 characters","type":["string","null"]},"pitchName":{"description":"A first pitch, e.g. \"1. Saha\"","type":["string","null"]},"indoor":{"description":"Whether that first pitch is indoors","type":"boolean"}},"required":["name","city"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean","description":"Checked by an admin (a partner venue); show the verified mark"},"pitches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"e.g. \"1. Saha\""},"indoor":{"type":"boolean"},"officialCamera":{"type":"boolean","description":"The pitch has the venue's own recorder: a match here can wait for its recording"}},"required":["id","name","indoor","officialCamera"]}}},"required":["id","name","city","district","address","verified","pitches"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"New venues are unverified until an admin checks them. When a venue with the same name, city and district is already listed (ignoring case and Turkish letters), that venue comes back instead of a copy. 422 with `fields` (`required`, `tooShort`, `tooLong`, `invalid` for hidden characters, `staffWord` when the name reads as staff: “Akıllı Futbol”, “Resmi”…). 429 RATE_LIMITED after 20 new venues in 24 hours (admins are not limited)."}},"/api/v1/venues/recent":{"get":{"operationId":"getVenuesRecent","summary":"Venues of your latest matches, then venues you added (at most five)","tags":["Venues"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean","description":"Checked by an admin (a partner venue); show the verified mark"},"pitches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"e.g. \"1. Saha\""},"indoor":{"type":"boolean"},"officialCamera":{"type":"boolean","description":"The pitch has the venue's own recorder: a match here can wait for its recording"}},"required":["id","name","indoor","officialCamera"]}}},"required":["id","name","city","district","address","verified","pitches"]}}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/venues/{id}":{"get":{"operationId":"getVenuesById","summary":"A venue with its pitches","tags":["Venues"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean","description":"Checked by an admin (a partner venue); show the verified mark"},"pitches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"e.g. \"1. Saha\""},"indoor":{"type":"boolean"},"officialCamera":{"type":"boolean","description":"The pitch has the venue's own recorder: a match here can wait for its recording"}},"required":["id","name","indoor","officialCamera"]}}},"required":["id","name","city","district","address","verified","pitches"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/venues/{id}/pitches":{"post":{"operationId":"postVenuesByIdPitches","summary":"Add a pitch to a venue","tags":["Venues"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","description":"1–40 characters, unique within the venue"},"indoor":{"type":"boolean"}},"required":["name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean","description":"Checked by an admin (a partner venue); show the verified mark"},"pitches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"e.g. \"1. Saha\""},"indoor":{"type":"boolean"},"officialCamera":{"type":"boolean","description":"The pitch has the venue's own recorder: a match here can wait for its recording"}},"required":["id","name","indoor","officialCamera"]}}},"required":["id","name","city","district","address","verified","pitches"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Admins, or the person who added the venue while it is unverified (403 otherwise). 409 `fields.name = taken` for a duplicate name."}},"/api/v1/venues/{id}/logo/{version}":{"get":{"operationId":"getVenuesByIdLogoByVersion","summary":"A venue's logo","tags":["Venues"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"version","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The logo","content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Public, no sign-in. Take the path from the venue's `logo` field rather than building it: the version names one picture for good, so the answer is cached for a year (`Cache-Control: public, max-age=31536000, immutable`). A replaced or removed logo's version answers 404 NOT_FOUND. A PNG, JPEG or WebP of at most 1024 pixels a side, its type read from the file's bytes when it was uploaded; sent with `X-Content-Type-Options: nosniff`, `Content-Security-Policy: default-src 'none'` and `Cross-Origin-Resource-Policy: same-site`."}},"/api/v1/venues/{id}/profile":{"get":{"operationId":"getVenuesByIdProfile","summary":"A venue's public page","tags":["Bookings"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string","description":"The page's address: /venues/<slug>"},"name":{"type":"string"},"verified":{"type":"boolean","description":"Checked by an admin; show the verified mark"},"district":{"type":["string","null"]},"city":{"type":["string","null"]},"address":{"type":["string","null"]},"description":{"description":"Plain text, line breaks kept","type":["string","null"]},"logo":{"description":"A path to the logo (immutable, cache it), or null","type":["string","null"]},"directions":{"type":"object","properties":{"google":{"type":"string","format":"uri"},"apple":{"type":"string","format":"uri"}},"required":["google","apple"],"description":"Driving directions: to the coordinates when known, else a search for the name and address"},"whatsapp":{"anyOf":[{"type":"object","properties":{"digits":{"type":"string","description":"International, without +"},"link":{"type":"string","format":"uri","description":"https://wa.me/<digits>"}},"required":["digits","link"]},{"type":"null"}],"description":"Only when the venue shows it"},"reservationsEnabled":{"type":"boolean","description":"The venue takes requests in the app; otherwise point people to WhatsApp"},"autoConfirm":{"type":"boolean","description":"Requests from people who already had a booking confirmed here are confirmed at once, while they hold fewer than 2 coming bookings and requests here"},"slotMinutes":{"anyOf":[{"type":"number","const":30},{"type":"number","const":60}],"description":"Start times every 30 or 60 minutes from opening"},"openingHours":{"type":"object","properties":{"mon":{"anyOf":[{"type":"object","properties":{"open":{"type":"string","description":"\"10:00\""},"close":{"type":"string","description":"\"01:00\"; at or before `open` means after midnight"}},"required":["open","close"]},{"type":"null"}]},"tue":{"anyOf":[{"type":"object","properties":{"open":{"type":"string","description":"\"10:00\""},"close":{"type":"string","description":"\"01:00\"; at or before `open` means after midnight"}},"required":["open","close"]},{"type":"null"}]},"wed":{"anyOf":[{"type":"object","properties":{"open":{"type":"string","description":"\"10:00\""},"close":{"type":"string","description":"\"01:00\"; at or before `open` means after midnight"}},"required":["open","close"]},{"type":"null"}]},"thu":{"anyOf":[{"type":"object","properties":{"open":{"type":"string","description":"\"10:00\""},"close":{"type":"string","description":"\"01:00\"; at or before `open` means after midnight"}},"required":["open","close"]},{"type":"null"}]},"fri":{"anyOf":[{"type":"object","properties":{"open":{"type":"string","description":"\"10:00\""},"close":{"type":"string","description":"\"01:00\"; at or before `open` means after midnight"}},"required":["open","close"]},{"type":"null"}]},"sat":{"anyOf":[{"type":"object","properties":{"open":{"type":"string","description":"\"10:00\""},"close":{"type":"string","description":"\"01:00\"; at or before `open` means after midnight"}},"required":["open","close"]},{"type":"null"}]},"sun":{"anyOf":[{"type":"object","properties":{"open":{"type":"string","description":"\"10:00\""},"close":{"type":"string","description":"\"01:00\"; at or before `open` means after midnight"}},"required":["open","close"]},{"type":"null"}]}},"required":["mon","tue","wed","thu","fri","sat","sun"],"description":"Istanbul clock; null: closed"},"today":{"type":"string","description":"The venue day open now (YYYY-MM-DD): the first day to offer. A venue day is named by the date it opens: Friday 10:00–02:00 holds Saturday 01:00"},"pitches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"indoor":{"type":"boolean"}},"required":["id","name","indoor"]}}},"required":["id","slug","name","verified","district","city","address","description","logo","directions","whatsapp","reservationsEnabled","autoConfirm","slotMinutes","openingHours","today","pitches"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Works signed out. 404 unless the venue has a login that is switched on (only those venues have a page)."}},"/api/v1/venues/{id}/availability":{"get":{"operationId":"getVenuesByIdAvailability","summary":"Free times on a venue day, per pitch","tags":["Bookings"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"date","in":"query","required":false,"description":"The venue day, YYYY-MM-DD (default: the venue day open now); from yesterday up to 31 days ahead","schema":{"type":"string"}},{"name":"durationMin","in":"query","required":false,"description":"60 (default), 90 or 120","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","enum":["60","90","120"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"date":{"type":"string"},"durationMin":{"type":"number"},"slotMinutes":{"type":"number"},"hours":{"anyOf":[{"type":"object","properties":{"open":{"type":"string","description":"\"10:00\""},"close":{"type":"string","description":"\"01:00\"; at or before `open` means after midnight"}},"required":["open","close"]},{"type":"null"}],"description":"That venue day's hours; null when closed"},"leadMinutes":{"type":"number","description":"How long before its start a time can still be asked for: 150 minutes, or 30 for someone the venue confirms at once"},"pitches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slots":{"type":"array","items":{"type":"object","properties":{"time":{"type":"string","description":"Istanbul wall clock, \"21:00\""},"date":{"type":"string","description":"The calendar date the time falls on (the next date after midnight)"},"startsAt":{"type":"string","description":"ISO 8601 (UTC)"},"nextDay":{"type":"boolean","description":"After the venue day's midnight: show it as \"Friday night 01:00 (Saturday 9 November)\""},"state":{"type":"string","enum":["free","requested","taken","soon","tooFar"],"description":"requested: someone asked and the venue has not answered; it can still be asked for"}},"required":["time","date","startsAt","nextDay","state"]}}},"required":["id","name","slots"]}}},"required":["date","durationMin","slotMinutes","hours","leadMinutes","pitches"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"States only, never who or why: confirmed bookings and blocked time make a time `taken`; requests the venue has not answered make it `requested` (still askable). With a bearer token the lead time is the person's own. 422 `fields.date = outOfRange`."}},"/api/v1/venues/{id}/reservations":{"post":{"operationId":"postVenuesByIdReservations","summary":"Ask a venue for a booking","tags":["Bookings"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"pitchId":{"type":"string"},"date":{"type":"string","description":"The venue day, YYYY-MM-DD"},"time":{"type":"string","description":"One of that day's start times (a slot's `time`), HH:MM"},"durationMin":{"anyOf":[{"type":"number","const":60},{"type":"number","const":90},{"type":"number","const":120}]},"teamId":{"description":"One of your teams: side A of the match made on confirmation","type":["string","null"]},"note":{"description":"Up to 300 characters, for the venue","type":["string","null"]},"phone":{"description":"Shared with this venue only and erased 30 days after the start (or when the booking is declined, cancelled or lapses). \"0532 123 45 67\", \"+90…\"","type":["string","null"]},"wantsMatch":{"description":"Default true: when the venue confirms, your match is made (this venue, pitch and time, your team on side A) and linked","type":"boolean"},"matchId":{"description":"Instead: one of your own matches at exactly this time with no booking yet, to link","type":["string","null"]}},"required":["pitchId","date","time","durationMin"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"venue":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"verified":{"type":"boolean"},"slug":{"description":"Null once the venue's page is gone","type":["string","null"]}},"required":["id","name","verified","slug"]},"pitch":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"startsAt":{"type":"string"},"endsAt":{"type":"string"},"durationMin":{"type":"number"},"venueDate":{"type":"string","description":"The venue day it belongs to"},"nextDay":{"type":"boolean"},"status":{"type":"string","enum":["pending","confirmed","declined","cancelled","lapsed"],"description":"lapsed: the venue did not answer by 2 hours before the start"},"answer":{"description":"The venue's words, or the app's own answer: \"@taken\" (another booking got the time), \"@blocked\" (the venue closed it), \"@loginOff\" (the venue stopped taking bookings), \"@matchCancelled\" (the match it was made for was called off)","type":["string","null"]},"cancelledBy":{"anyOf":[{"type":"string","enum":["person","venue"]},{"type":"null"}]},"team":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},{"type":"null"}]},"match":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},{"type":"null"}],"description":"The match made from (or linked to) the booking, while it is not cancelled"},"wantsMatch":{"type":"boolean"},"canCancel":{"type":"boolean"},"canMakeMatch":{"type":"boolean","description":"Confirmed, not started and without a match: POST /api/v1/matches with its time, place and `reservationId`"}},"required":["id","venue","pitch","startsAt","endsAt","durationMin","venueDate","nextDay","status","answer","cancelledBy","team","match","wantsMatch","canCancel","canMakeMatch"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"The booking waits for the venue (`pending`) and lapses 2 hours before its start if not answered; it is `confirmed` at once where the venue auto-confirms and you already had a booking confirmed there. 404 for a venue without a page (no login, or switched off). 409 `fields.form`: `bookingsOff` (the venue takes no requests now; use its WhatsApp), `alreadyAsked`, `tooManyOpen` (2 requests already waiting at this venue); `fields.time = taken`. 422 `fields`: `pitchId.invalid`, `teamId.notMember`, `phone.invalid`, `matchId` (`invalid`, `booked`, `otherTime`, `otherPlace`), `time` (`closed`, `offGrid`, `soon`, `tooFar`). 429 RATE_LIMITED after 6 requests in an hour (refused requests do not count)."}},"/api/v1/reservations":{"get":{"operationId":"getReservations","summary":"Your bookings","tags":["Bookings"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"upcoming":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"venue":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"verified":{"type":"boolean"},"slug":{"description":"Null once the venue's page is gone","type":["string","null"]}},"required":["id","name","verified","slug"]},"pitch":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"startsAt":{"type":"string"},"endsAt":{"type":"string"},"durationMin":{"type":"number"},"venueDate":{"type":"string","description":"The venue day it belongs to"},"nextDay":{"type":"boolean"},"status":{"type":"string","enum":["pending","confirmed","declined","cancelled","lapsed"],"description":"lapsed: the venue did not answer by 2 hours before the start"},"answer":{"description":"The venue's words, or the app's own answer: \"@taken\" (another booking got the time), \"@blocked\" (the venue closed it), \"@loginOff\" (the venue stopped taking bookings), \"@matchCancelled\" (the match it was made for was called off)","type":["string","null"]},"cancelledBy":{"anyOf":[{"type":"string","enum":["person","venue"]},{"type":"null"}]},"team":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},{"type":"null"}]},"match":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},{"type":"null"}],"description":"The match made from (or linked to) the booking, while it is not cancelled"},"wantsMatch":{"type":"boolean"},"canCancel":{"type":"boolean"},"canMakeMatch":{"type":"boolean","description":"Confirmed, not started and without a match: POST /api/v1/matches with its time, place and `reservationId`"}},"required":["id","venue","pitch","startsAt","endsAt","durationMin","venueDate","nextDay","status","answer","cancelledBy","team","match","wantsMatch","canCancel","canMakeMatch"]}},"past":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"venue":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"verified":{"type":"boolean"},"slug":{"description":"Null once the venue's page is gone","type":["string","null"]}},"required":["id","name","verified","slug"]},"pitch":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"startsAt":{"type":"string"},"endsAt":{"type":"string"},"durationMin":{"type":"number"},"venueDate":{"type":"string","description":"The venue day it belongs to"},"nextDay":{"type":"boolean"},"status":{"type":"string","enum":["pending","confirmed","declined","cancelled","lapsed"],"description":"lapsed: the venue did not answer by 2 hours before the start"},"answer":{"description":"The venue's words, or the app's own answer: \"@taken\" (another booking got the time), \"@blocked\" (the venue closed it), \"@loginOff\" (the venue stopped taking bookings), \"@matchCancelled\" (the match it was made for was called off)","type":["string","null"]},"cancelledBy":{"anyOf":[{"type":"string","enum":["person","venue"]},{"type":"null"}]},"team":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},{"type":"null"}]},"match":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},{"type":"null"}],"description":"The match made from (or linked to) the booking, while it is not cancelled"},"wantsMatch":{"type":"boolean"},"canCancel":{"type":"boolean"},"canMakeMatch":{"type":"boolean","description":"Confirmed, not started and without a match: POST /api/v1/matches with its time, place and `reservationId`"}},"required":["id","venue","pitch","startsAt","endsAt","durationMin","venueDate","nextDay","status","answer","cancelledBy","team","match","wantsMatch","canCancel","canMakeMatch"]}}},"required":["upcoming","past"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"`upcoming`: coming bookings whatever their answer (until they start; a confirmed one until it ends), soonest first. `past`: those of the last 30 days, latest first."}},"/api/v1/reservations/{id}":{"delete":{"operationId":"deleteReservationsById","summary":"Cancel one of your bookings","tags":["Bookings"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"venue":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"verified":{"type":"boolean"},"slug":{"description":"Null once the venue's page is gone","type":["string","null"]}},"required":["id","name","verified","slug"]},"pitch":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"startsAt":{"type":"string"},"endsAt":{"type":"string"},"durationMin":{"type":"number"},"venueDate":{"type":"string","description":"The venue day it belongs to"},"nextDay":{"type":"boolean"},"status":{"type":"string","enum":["pending","confirmed","declined","cancelled","lapsed"],"description":"lapsed: the venue did not answer by 2 hours before the start"},"answer":{"description":"The venue's words, or the app's own answer: \"@taken\" (another booking got the time), \"@blocked\" (the venue closed it), \"@loginOff\" (the venue stopped taking bookings), \"@matchCancelled\" (the match it was made for was called off)","type":["string","null"]},"cancelledBy":{"anyOf":[{"type":"string","enum":["person","venue"]},{"type":"null"}]},"team":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},{"type":"null"}]},"match":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},{"type":"null"}],"description":"The match made from (or linked to) the booking, while it is not cancelled"},"wantsMatch":{"type":"boolean"},"canCancel":{"type":"boolean"},"canMakeMatch":{"type":"boolean","description":"Confirmed, not started and without a match: POST /api/v1/matches with its time, place and `reservationId`"}},"required":["id","venue","pitch","startsAt","endsAt","durationMin","venueDate","nextDay","status","answer","cancelledBy","team","match","wantsMatch","canCancel","canMakeMatch"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Waiting or confirmed, before it starts; the time is free again. 404 for someone else's booking; 409 `fields.form`: `notActive`, `started`."}},"/api/v1/matches/{id}/score":{"get":{"operationId":"getMatchesByIdScore","summary":"The score card: goals, who played and the reported scorers","tags":["Score"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"matchId":{"type":"string"},"status":{"type":"string","enum":["planned","played","cancelled"]},"scoreState":{"type":"string","enum":["none","entered","confirmed"]},"recording":{"type":"string","enum":["upload","official"],"description":"official: the pitch's camera records the match; nobody needs to upload"},"startsAt":{"type":"string","description":"ISO 8601 (UTC)"},"kickedOff":{"type":"boolean"},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]}},"required":["side","label","goals"]},"B":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]}},"required":["side","label","goals"]}},"required":["A","B"]},"ownGoals":{"type":"object","properties":{"A":{"type":["number","null"]},"B":{"type":["number","null"]}},"required":["A","B"],"description":"Per side, how many of that side's goals the other side put into its own net. They count for nobody's goals (profiles and stats count reported scorers only). Null when not given, and until the score is entered"},"people":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"rsvp":{"type":"string","enum":["invited","yes","maybe","no"]},"attended":{"type":"boolean","description":"Played in the match"},"goalsReported":{"description":"Self-reported goals; null when not reported","type":["number","null"]},"isOrganizer":{"type":"boolean"},"markable":{"type":"boolean","description":"You may mark them as played now: guests, people who said yes or maybe or played already, members of a team in the match, and people who share a team or a match with you (a public profile alone does not count). Always false when you cannot enter the score"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["playerId","displayName","handle","kind","side","rsvp","attended","goalsReported","isOrganizer","markable","photo"]},"description":"Everyone in the match, by name"},"scorers":{"type":"object","properties":{"A":{"type":"boolean"},"B":{"type":"boolean"}},"required":["A","B"],"description":"Whether each side's scorers were reported: a player of the side has a count, or its own goals were given"},"hasVideo":{"type":"boolean"},"lastChange":{"anyOf":[{"type":"object","properties":{"at":{"type":"string"},"by":{"description":"Who saved it; null for the Akıllı Futbol team","type":["string","null"]},"edited":{"type":"boolean","description":"A correction rather than the first entry"}},"required":["at","by","edited"]},{"type":"null"}]},"version":{"description":"Identifies the last save (null before the first); send it back as `basedOn` with PUT","type":["string","null"]},"viewer":{"type":"object","properties":{"playerId":{"type":"string"},"isAdmin":{"type":"boolean"},"canManage":{"type":"boolean","description":"Organiser, side captains and admins"},"canEdit":{"type":"boolean","description":"May enter or correct the score now"},"blockedBy":{"anyOf":[{"type":"string","enum":["cancelled","beforeKickoff"]},{"type":"null"}],"description":"Why a manager cannot enter the score now"},"attended":{"type":"boolean"},"canUpload":{"type":"boolean","description":"May upload the match video"}},"required":["playerId","isAdmin","canManage","canEdit","blockedBy","attended","canUpload"]}},"required":["matchId","status","scoreState","recording","startsAt","kickedOff","sides","ownGoals","people","scorers","hasVideo","lastChange","version","viewer"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For everyone who can see the match. Errors: 401 signed out; 404 the match does not exist or you are not part of it."},"put":{"operationId":"putMatchesByIdScore","summary":"Enter or correct the score and who played (managers)","tags":["Score"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"goals":{"type":"object","properties":{"A":{"type":"integer","minimum":0,"maximum":99},"B":{"type":"integer","minimum":0,"maximum":99}},"required":["A","B"]},"ownGoals":{"description":"Per side, how many of that side's goals the other side put into its own net. They count for nobody's goals (profiles and stats count reported scorers only). Leave a side out (or null) when not given; 0 is given and checks the side's scorers like a count does","anyOf":[{"type":"object","properties":{"A":{"anyOf":[{"type":"integer","minimum":0,"maximum":99},{"type":"null"}]},"B":{"anyOf":[{"type":"integer","minimum":0,"maximum":99},{"type":"null"}]}}},{"type":"null"}]},"attendance":{"maxItems":80,"type":"array","items":{"type":"object","properties":{"playerId":{"description":"Someone already in the match (see `people`)","type":"string"},"username":{"description":"A registered player by username; joins the match if not in it yet","type":"string"},"guestName":{"description":"Someone without an account: a new guest player, 2–40 characters","type":"string"},"side":{"type":"string","enum":["A","B"]},"goals":{"description":"Self-reported goals; leave out (or null) when not reported","anyOf":[{"type":"integer","minimum":0,"maximum":99},{"type":"null"}]}},"required":["side"]},"description":"Everyone who played, each with exactly one of playerId, username or guestName. Anyone in the match who is not listed did not play"},"basedOn":{"description":"The card's `version` you started from (null for a new score). When sent, the save is refused with 409 `changed` if someone saved the score since; leave it out to save regardless","type":["string","null"]}},"required":["goals","attendance"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"matchId":{"type":"string"},"status":{"type":"string","enum":["planned","played","cancelled"]},"scoreState":{"type":"string","enum":["none","entered","confirmed"]},"recording":{"type":"string","enum":["upload","official"],"description":"official: the pitch's camera records the match; nobody needs to upload"},"startsAt":{"type":"string","description":"ISO 8601 (UTC)"},"kickedOff":{"type":"boolean"},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]}},"required":["side","label","goals"]},"B":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"description":"Null until the score is entered","type":["number","null"]}},"required":["side","label","goals"]}},"required":["A","B"]},"ownGoals":{"type":"object","properties":{"A":{"type":["number","null"]},"B":{"type":["number","null"]}},"required":["A","B"],"description":"Per side, how many of that side's goals the other side put into its own net. They count for nobody's goals (profiles and stats count reported scorers only). Null when not given, and until the score is entered"},"people":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"rsvp":{"type":"string","enum":["invited","yes","maybe","no"]},"attended":{"type":"boolean","description":"Played in the match"},"goalsReported":{"description":"Self-reported goals; null when not reported","type":["number","null"]},"isOrganizer":{"type":"boolean"},"markable":{"type":"boolean","description":"You may mark them as played now: guests, people who said yes or maybe or played already, members of a team in the match, and people who share a team or a match with you (a public profile alone does not count). Always false when you cannot enter the score"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["playerId","displayName","handle","kind","side","rsvp","attended","goalsReported","isOrganizer","markable","photo"]},"description":"Everyone in the match, by name"},"scorers":{"type":"object","properties":{"A":{"type":"boolean"},"B":{"type":"boolean"}},"required":["A","B"],"description":"Whether each side's scorers were reported: a player of the side has a count, or its own goals were given"},"hasVideo":{"type":"boolean"},"lastChange":{"anyOf":[{"type":"object","properties":{"at":{"type":"string"},"by":{"description":"Who saved it; null for the Akıllı Futbol team","type":["string","null"]},"edited":{"type":"boolean","description":"A correction rather than the first entry"}},"required":["at","by","edited"]},{"type":"null"}]},"version":{"description":"Identifies the last save (null before the first); send it back as `basedOn` with PUT","type":["string","null"]},"viewer":{"type":"object","properties":{"playerId":{"type":"string"},"isAdmin":{"type":"boolean"},"canManage":{"type":"boolean","description":"Organiser, side captains and admins"},"canEdit":{"type":"boolean","description":"May enter or correct the score now"},"blockedBy":{"anyOf":[{"type":"string","enum":["cancelled","beforeKickoff"]},{"type":"null"}],"description":"Why a manager cannot enter the score now"},"attended":{"type":"boolean"},"canUpload":{"type":"boolean","description":"May upload the match video"}},"required":["playerId","isAdmin","canManage","canEdit","blockedBy","attended","canUpload"]}},"required":["matchId","status","scoreState","recording","startsAt","kickedOff","sides","ownGoals","people","scorers","hasVideo","lastChange","version","viewer"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"The organiser, the side captains and admins; from kick-off (admins at any time), never for a cancelled match. One transaction writes the goals and own goals, marks the match played, sets everyone's attendance and side (people not listed did not play) and adds new guests and players. The first save is audited as `score.set`, corrections as `score.edit` with before and after (goals, own goals and who played). Man-of-the-match votes from or for someone who no longer played are dropped.\n\nReported goals are optional per side. When any player of a side has a count or the side's `ownGoals` is given, the side's counts plus its own goals must add up to its goals (the players without a count are stored as 0). Example: 3–2 where one of A's goals was an own goal by B: `ownGoals: { A: 1 }` and A's players' counts add up to 2.\n\nPlaying a match opens a teammates-only profile to the others who played, so only people with `markable` true (or a username GET …/score/lookup accepts) can be marked as played; anyone else answers yes themselves before the score is in, or comes as a guest. Each username counts against the same limit as the lookups (60 per 10 minutes).\n\n403 for people who may see the match but not manage it. 409 `fields.form` = `cancelled`, `beforeKickoff` or `changed` (someone saved since your `basedOn`). 422 `fields`: `scorers.A` / `scorers.B` = `mismatch` (the side's counts plus own goals do not add up to its goals, own goals beyond its goals included); `attendance.N` = `invalid` (not exactly one of playerId, username, guestName); `attendance.N.playerId` = `invalid` (not an id), `notInMatch`, `duplicate` or `notConnected`; `attendance.N.username` = `notFound`, `duplicate` or `notConnected`; `attendance.N.guestName` = `required`, `tooShort`, `tooLong` or `invalid`; `goals.A`, `goals.B`, `ownGoals.A`, `ownGoals.B`, `attendance.N.goals` = `invalid` (0–99); `ownGoals` = `invalid` (not an object); `basedOn` = `invalid`. 429 too many usernames.\n\nErrors: 401 signed out; 404 the match does not exist or you are not part of it."}},"/api/v1/matches/{id}/score/lookup":{"get":{"operationId":"getMatchesByIdScoreLookup","summary":"Look up a registered player by username, before adding them to the score card (managers)","tags":["Score"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"username","in":"query","required":true,"description":"As typed: a leading @, surrounding spaces and letter case do not matter","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"type":"string"},"inMatch":{"type":"boolean","description":"Already in the match (invited, answered or played): send their playerId or username with PUT"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"Their side in the match, when they have one"}},"required":["playerId","displayName","handle","inMatch","side"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"What the score page asks before it lists someone as played: who the username belongs to, and whether they are in the match already and on which side. Managers only, while the score can be entered (from kick-off; admins at any time; never for a cancelled match). Each lookup counts against the limit of 60 per 10 minutes shared with the usernames sent with PUT.\n\n403 for people who may see the match but not manage it. 409 `fields.form` = `cancelled` or `beforeKickoff`. 422 `fields.username` = `required`, `tooLong`, `notFound` (no registered player has it) or `notConnected` (they have not said they are coming and you share no team or match: they answer yes themselves first, or come as a guest). 429 too many lookups.\n\nErrors: 401 signed out; 404 the match does not exist or you are not part of it."}},"/api/v1/matches/{id}/motm":{"get":{"operationId":"getMatchesByIdMotm","summary":"Man of the match: the tally, the leader and your vote","tags":["Score"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"matchId":{"type":"string"},"available":{"type":"boolean","description":"The score is in and the match was not cancelled"},"open":{"type":"boolean","description":"Votes can be cast or changed now"},"closesAt":{"type":"string","description":"Kick-off plus 7 days (ISO 8601)"},"totalVotes":{"type":"number"},"candidates":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"votes":{"type":"number"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["playerId","displayName","handle","kind","side","votes","photo"]},"description":"Everyone who played: most votes first, then by name"},"leaders":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"votes":{"type":"number"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["playerId","displayName","handle","kind","side","votes","photo"]},"description":"The most voted; several on a tie, none before the first vote"},"viewer":{"type":"object","properties":{"playerId":{"type":"string"},"attended":{"type":"boolean"},"canVote":{"type":"boolean"},"vote":{"description":"The playerId you voted for","type":["string","null"]}},"required":["playerId","attended","canVote","vote"]}},"required":["matchId","available","open","closesAt","totalVotes","candidates","leaders","viewer"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Votes are anonymous: you see the counts and your own vote. Errors: 401 signed out; 404 the match does not exist or you are not part of it."},"post":{"operationId":"postMatchesByIdMotm","summary":"Vote for the man of the match (people who played)","tags":["Score"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"playerId":{"type":"string","description":"Someone else who played"}},"required":["playerId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"matchId":{"type":"string"},"available":{"type":"boolean","description":"The score is in and the match was not cancelled"},"open":{"type":"boolean","description":"Votes can be cast or changed now"},"closesAt":{"type":"string","description":"Kick-off plus 7 days (ISO 8601)"},"totalVotes":{"type":"number"},"candidates":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"votes":{"type":"number"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["playerId","displayName","handle","kind","side","votes","photo"]},"description":"Everyone who played: most votes first, then by name"},"leaders":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"votes":{"type":"number"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["playerId","displayName","handle","kind","side","votes","photo"]},"description":"The most voted; several on a tie, none before the first vote"},"viewer":{"type":"object","properties":{"playerId":{"type":"string"},"attended":{"type":"boolean"},"canVote":{"type":"boolean"},"vote":{"description":"The playerId you voted for","type":["string","null"]}},"required":["playerId","attended","canVote","vote"]}},"required":["matchId","available","open","closesAt","totalVotes","candidates","leaders","viewer"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"One vote each; voting again changes it. Open once the score is in, until 7 days after kick-off. 403 `fields.form = notAttended` when you did not play. 409 `fields.form` = `noScore`, `closed` or `cancelled`. 422 `fields.playerId` = `self` or `notAttended`. Errors: 401 signed out; 404 the match does not exist or you are not part of it."}},"/api/v1/matches/{id}/uploads":{"post":{"operationId":"postMatchesByIdUploads","summary":"Start or resume the match video upload","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"fileName":{"type":"string","minLength":1,"maxLength":255,"description":"The picked file's name, e.g. mac.mp4. An extension other than a video container's (such as .m3u8) is refused"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"Bytes; at most 8 GiB"},"contentType":{"type":"string","description":"The file's MIME type; must be video/*"},"fingerprint":{"type":"string","minLength":1,"maxLength":512,"description":"name:size:lastModified of the file. Starting again with the same fingerprint resumes the unfinished upload"}},"required":["fileName","size","contentType","fingerprint"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"uploadId":{"type":"string"},"key":{"type":"string","description":"The object key in the bucket"},"partSize":{"type":"number","description":"Bytes per part (64 MiB); the last part holds the rest"},"partCount":{"type":"number"},"uploadedParts":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"number"},"etag":{"type":"string"},"size":{"type":"number"}},"required":["partNumber","etag","size"]},"description":"Parts the bucket already has (after a resume): upload only the others"},"resumed":{"type":"boolean","description":"True when this continues an unfinished upload of the same file"}},"required":["uploadId","key","partSize","partCount","uploadedParts","resumed"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"The video goes straight to the bucket in parts; no bytes pass through this API.\n\n1. POST here with the file's name, size, type and fingerprint (name:size:lastModified).\n\n2. POST …/parts with the part numbers still missing (at most 100 per call) and PUT each 64 MiB slice of the file to its URL; keep the ETag response header. Each link is signed for its part's exact length (the last part holds the rest), so send exactly that slice. Retry failed parts; sign again when the links expire.\n\n3. POST …/complete with every { partNumber, etag }. The analysis starts (status INGESTING).\n\nTo resume after the app restarts, POST here again with the same fingerprint: the same upload comes back with `uploadedParts`.\n\nErrors: 404 NOT_FOUND for people outside the match (as if it did not exist); 409 CONFLICT with fields.form scoreFirst (enter the score first), matchCancelled or venueDelivering (the venue is sending the recording); 403 FORBIDDEN with fields.form uploadNotAllowed (only people who played), officialRecording (the venue or an admin delivers the official recording) or replaceNotAllowed (the match has a video; only the organiser, side captains and admins replace it); 422 INVALID with fields.size tooLarge (over 8 GiB), fields.contentType notVideo, or fields.fileName notVideo (an extension other than a video container's, such as .m3u8)."}},"/api/v1/matches/{id}/uploads/{uploadId}":{"delete":{"operationId":"deleteMatchesByIdUploadsByUploadId","summary":"Cancel an unfinished upload","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"uploadId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"uploadId":{"type":"string"},"status":{"type":"string","enum":["aborted","failed"]}},"required":["uploadId","status"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Only the person who started it. Drops its parts from the bucket, or its file when a completion had finished it. 409 uploadCompleted once it is complete. 403 fields.form notYourUpload for anyone but the person who started the upload (and admins)."}},"/api/v1/matches/{id}/uploads/{uploadId}/parts":{"post":{"operationId":"postMatchesByIdUploadsByUploadIdParts","summary":"Presigned URLs for upload parts","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"uploadId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"partNumbers":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"integer","minimum":1,"maximum":10000},"description":"1-based part numbers, at most 100 per call"}},"required":["partNumbers"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"parts":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"number"},"url":{"type":"string","description":"PUT the part's bytes here, then read the ETag response header"}},"required":["partNumber","url"]}},"expiresAt":{"type":"string","description":"ISO time; sign again after it"}},"required":["parts","expiresAt"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Links are valid for an hour and accept exactly their part's bytes. 409 uploadClosed when the upload was cancelled or completed. The upload rules are checked again, so a cancelled match (409 matchCancelled) or someone else's video landing first (403 replaceNotAllowed) stops the upload: cancel it with DELETE. 403 fields.form notYourUpload for anyone but the person who started the upload (and admins)."}},"/api/v1/matches/{id}/uploads/{uploadId}/complete":{"post":{"operationId":"postMatchesByIdUploadsByUploadIdComplete","summary":"Finish the upload and start the analysis","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"uploadId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"parts":{"minItems":1,"maxItems":10000,"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"integer","minimum":1,"maximum":10000},"etag":{"type":"string","minLength":1,"maxLength":200,"description":"The ETag header of the part's PUT response"}},"required":["partNumber","etag"]}}},"required":["parts"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"uploadId":{"type":"string"},"videoAssetId":{"type":["string","null"]},"analysisId":{"type":["string","null"]},"status":{"anyOf":[{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},{"type":"null"}]},"replaced":{"type":"boolean","description":"True when this video replaced the match's previous one and its analysis; the previous video's files are deleted, and links issued for it stop working"}},"required":["uploadId","videoAssetId","analysisId","status","replaced"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Checks the parts and the stored size, then creates the video, the analysis and its first job in one step. Calling it again returns the same result. 409 with fields.form partsMissing, partMismatch or sizeMismatch: upload the missing parts (resume) and complete again. 403 replaceNotAllowed when someone else's video became the match's first: this upload is then closed and its file deleted. 403 fields.form notYourUpload for anyone but the person who started the upload (and admins)."}},"/api/v1/matches/{id}/analysis":{"get":{"operationId":"getMatchesByIdAnalysis","summary":"The match's video and analysis status","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"uploadedBytes","in":"query","required":false,"description":"true to fill pendingUpload.uploadedBytes (how much of your unfinished upload the bucket holds). It asks the bucket, so leave it out when polling the analysis","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"boolean"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"matchId":{"type":"string"},"matchStatus":{"type":"string","enum":["planned","played","cancelled"]},"scoreState":{"type":"string","enum":["none","entered","confirmed"],"description":"Uploads open once the score is entered"},"recording":{"type":"string","enum":["upload","official"],"description":"official: the pitch's camera records the match and the venue (or an admin) delivers the video"},"analysis":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},"progress":{"type":"number","description":"0..1 within the current stage"},"error":{"type":["string","null"]},"failedStage":{"anyOf":[{"type":"string","enum":["ingest","track","stats"]},{"type":"null"}],"description":"The stage that failed, when status is FAILED"},"updatedAt":{"type":"string"}},"required":["id","status","progress","error","failedStage","updatedAt"]},{"type":"null"}]},"video":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"ready":{"type":"boolean","description":"The playable copy exists: GET /video gives a link"},"fileName":{"type":"string"},"sizeBytes":{"type":"number"},"uploadedBy":{"type":"string","description":"The person who uploaded it, or the venue that delivered it (byVenue)"},"byVenue":{"type":"boolean","description":"The venue delivered it from its pitch admin panel (ADR-005)"},"uploadedAt":{"type":["string","null"]},"durationS":{"type":["number","null"]},"width":{"type":["number","null"]},"height":{"type":["number","null"]}},"required":["id","ready","fileName","sizeBytes","uploadedBy","byVenue","uploadedAt","durationS","width","height"]},{"type":"null"}]},"pendingUpload":{"anyOf":[{"type":"object","properties":{"uploadId":{"type":"string"},"fileName":{"type":"string"},"sizeBytes":{"type":"number"},"fingerprint":{"type":"string"},"startedAt":{"type":"string"},"uploadedBytes":{"description":"Bytes the bucket holds; null unless asked with ?uploadedBytes=true (or when the bucket no longer knows the upload)","type":["number","null"]}},"required":["uploadId","fileName","sizeBytes","fingerprint","startedAt","uploadedBytes"]},{"type":"null"}],"description":"The caller's own unfinished upload; start again with the same fingerprint to resume it"},"venueDelivery":{"anyOf":[{"type":"object","properties":{"venueName":{"type":"string"},"state":{"type":"string","enum":["delivering","possible"],"description":"delivering: the venue is uploading the recording now; possible: its official camera records this pitch and it can send the recording"}},"required":["venueName","state"]},{"type":"null"}],"description":"The venue's part in this match's recording, when it has a pitch admin panel"},"can":{"type":"object","properties":{"upload":{"type":"boolean","description":"May start the first upload now"},"replace":{"type":"boolean","description":"May upload a new video over the existing one"},"manage":{"type":"boolean","description":"Organiser, side captains and admins: prep, labels, retry"},"watch":{"type":"boolean"},"reject":{"type":"boolean","description":"Managers may send back a recording the venue delivered (\"Bu bizim maçımız değil\") until labelling starts: POST …/video/reject"}},"required":["upload","replace","manage","watch","reject"]}},"required":["matchId","matchStatus","scoreState","recording","analysis","video","pendingUpload","venueDelivery","can"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For anyone involved in the match (404 for everyone else). Stages: INGESTING → NEEDS_PREP (managers mark the pitch) → TRACKING → NEEDS_LABELS (who is who) → COMPUTING → PUBLISHED, or FAILED. Poll every few seconds while the status is INGESTING, TRACKING or COMPUTING."}},"/api/v1/matches/{id}/analysis/retry":{"post":{"operationId":"postMatchesByIdAnalysisRetry","summary":"Retry the failed stage","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"analysisId":{"type":"string"},"status":{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},"kind":{"type":"string","enum":["ingest","track","stats"]}},"required":["analysisId","status","kind"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Organiser, side captains and admins (403 retryNotAllowed for other people in the match, 404 outside it). The job's payload is rebuilt from the analysis, never copied from the failed job. 409 notFailed unless the status is FAILED; 409 alreadyQueued."}},"/api/v1/matches/{id}/video":{"get":{"operationId":"getMatchesByIdVideo","summary":"A link to watch the match video","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"url":{"type":"string","description":"A presigned GET link to the 720p copy, valid for 2 hours; supports range requests"},"expiresAt":{"type":"string"},"durationS":{"type":["number","null"]},"width":{"type":["number","null"]},"height":{"type":["number","null"]}},"required":["url","expiresAt","durationS","width","height"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For people who attended and managers (403 watchNotAllowed for other people in the match, 404 outside it); 404 notReady until the playable copy exists. Every link issued is written to the audit log."}},"/api/v1/matches/{id}/video/reject":{"post":{"operationId":"postMatchesByIdVideoReject","summary":"Send back a recording the venue delivered","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"matchId":{"type":"string"},"analysisId":{"type":"string"},"uploadId":{"type":"string"}},"required":["matchId","analysisId","uploadId"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"\"Bu bizim maçımız değil\" (ADR-005): for the organiser, side captains and admins (403 rejectNotAllowed for other people in the match, 404 outside it), until labelling starts. The video, its analysis and files are deleted, and the match waits for the venue's recording again. Audited as video.reject. 409 with fields.form noVideo, notVenueDelivery (people uploaded it) or labellingStarted."}},"/api/v1/matches/{id}/overlay":{"get":{"operationId":"getMatchesByIdOverlay","summary":"Who is who on the video, and the goals","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"matchId":{"type":"string"},"analysisId":{"type":["string","null"]},"generation":{"description":"Committed tracking run; must match replay/camera metadata. Pass it as the generation query to layer files, and reload names plus metadata on 409 replayChanged","type":["string","null"]},"status":{"anyOf":[{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},{"type":"null"}]},"ready":{"type":"boolean","description":"Committed tracking exists (including the previous saved result during a rerun); false before the first result"},"fps":{"description":"The video's frame rate: the frame on screen is round(mediaTime × fps)","type":["number","null"]},"durationS":{"type":["number","null"]},"kickoffS":{"type":"number","description":"Kick-off in seconds into the video; minutes count from it"},"sides":{"type":"object","properties":{"A":{"type":"string"},"B":{"type":"string"}},"required":["A","B"],"description":"The sides' labels"},"finalScore":{"type":"object","properties":{"A":{"type":["number","null"]},"B":{"type":["number","null"]}},"required":["A","B"],"description":"The score entered after the match; null per side before it is entered"},"can":{"type":"object","properties":{"manage":{"type":"boolean","description":"Organiser, side captains and admins: they also get the candidates"}},"required":["manage"]},"tracks":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"playerId":{"description":"The player this tracklet was labelled as; null while unlabelled (or labelled mixed), and for a player whose profile the caller may not see (drawn on their side, without a name)","type":["string","null"]},"name":{"description":"The short name to draw above the player (\"Oğuz\", \"Ali K.\" when two share a first name); null until labelled, and for a player whose profile the caller may not see","type":["string","null"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The player's side in the match once labelled, else the tracker's guess from the shirt colour; null when unknown"},"keeper":{"type":"boolean","description":"The tracker took this person for a goalkeeper (draw a distinct marker)"}},"required":["playerId","name","side","keeper"]},"description":"Track id of the layer (the tracklet's ordinal) -> who it is. Tracks with nothing known are left out: draw them without a side"},"players":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"name":{"type":"string"},"short":{"type":"string"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]}},"required":["name","short","side"]},"description":"Player id -> full and short name and side in the match, for the players the tracks name"},"hidden":{"type":"array","items":{"type":"number"},"description":"Track ids labelled not a player (referees, spectators): leave them out"},"goals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"atS":{"type":"number","description":"Seconds into the match video"},"frame":{"description":"The proxy's frame: frame = round(mediaTime × fps)","type":["number","null"]},"minute":{"type":"number","description":"Match minute from kick-off: 0:00–0:59 is the 1st"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side credited with the goal (an own goal counts for the side that did not put it in)"},"ownGoal":{"type":"boolean"},"scorer":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"name":{"type":"string"}},"required":["playerId","name"]},{"type":"null"}],"description":"Who scored; for an own goal the opponent who put it in; null when nobody was listed on that side"},"assist":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"name":{"type":"string"}},"required":["playerId","name"]},{"type":"null"}]},"score":{"type":"object","properties":{"A":{"type":"number"},"B":{"type":"number"}},"required":["A","B"],"description":"The score right after this goal"}},"required":["id","atS","frame","minute","side","ownGoal","scorer","assist","score"]},"description":"Confirmed goals in video order: show a banner at each and mark them on the timeline"},"candidates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"atS":{"type":"number"},"frame":{"type":["number","null"]},"minute":{"type":"number"}},"required":["id","atS","frame","minute"]},"description":"For managers: goals the analysis found that wait for \"Bu gol mü?\" (POST /goals/{goalId}); empty for everyone else"}},"required":["matchId","analysisId","generation","status","ready","fps","durationS","kickoffS","sides","finalScore","can","tracks","players","hidden","goals","candidates"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For people who attended and the organiser, side captains and admins (403 watchNotAllowed for others in the match, 404 for everyone else). Never cached: labels and goal decisions change it.\n\nA new processing run may reuse numeric track ids. Match this response's generation to the replay or camera metadata, and request files with ?generation=<generation>. A 409 replayChanged means reload both names and metadata before drawing new positions.\n\nDraw it with the layer files (GET /layer/index.json, then /layer/chunk_<n>.json, see the Kim kim? tag): each box `[trackId, x0, y0, x1, y1]` is in the index's width × height pixels, so scale it to the displayed video (with its letterboxing). A foot marker goes at the box's bottom centre in the side's colour (orange for A, blue for B), with `tracks[trackId].name` above it once labelled; goalkeepers (`keeper`, or the index's `keepers`) get a marker of their own. The ball is a frame's `b` ([x, y, 1 when estimated]); `o` (or the index's possession `spells`) is who has it. The index's `passes` go from passer to receiver between their `start` and `end` frames (`completed` false: the ball was lost; null: not known, one of the two teams is unknown).\n\nThe frame on screen is round(mediaTime × fps). Show each confirmed goal's banner for a few seconds from its frame."}},"/api/v1/matches/{id}/replay":{"get":{"operationId":"getMatchesByIdReplay","summary":"Saved top-down replay and camera recordings","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"matchId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"analysisId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"generation":{"type":["string","null"]},"demoAliases":{"type":"boolean"},"ready":{"type":"boolean"},"index":{"anyOf":[{"type":"object","properties":{"version":{"type":"number","const":1},"fps":{"type":"number","exclusiveMinimum":0},"stride":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"start_frame":{"type":"integer","minimum":0,"maximum":9007199254740991},"end_frame":{"type":"integer","minimum":0,"maximum":9007199254740991},"chunk_frames":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunks":{"type":"array","items":{"type":"integer","minimum":0,"maximum":9007199254740991}},"pitch":{"type":"object","properties":{"length_m":{"type":"number","exclusiveMinimum":0},"width_m":{"type":"number","exclusiveMinimum":0},"measured":{"type":"boolean"}},"required":["length_m","width_m","measured"],"additionalProperties":false}},"required":["version","fps","stride","start_frame","end_frame","chunk_frames","chunks","pitch"],"additionalProperties":false},{"type":"null"}]},"cameras":{"type":"array","items":{"type":"object","properties":{"generation":{"type":["string","null"]},"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"ordinal":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"label":{"type":"string"},"offsetS":{"type":"number","description":"Master time = camera video time + offsetS; camera layer frames use this camera's fps"},"fps":{"type":["number","null"]},"durationS":{"type":["number","null"]},"width":{"type":["number","null"]},"height":{"type":["number","null"]},"ready":{"type":"boolean","description":"The playable video exists"},"layerReady":{"type":"boolean","description":"The camera's saved position layer exists"},"calibrationQuality":{"anyOf":[{"type":"string","enum":["GOOD","WEAK"]},{"type":"null"}]},"calibrationRmsPx":{"type":["number","null"]}},"required":["generation","id","ordinal","label","offsetS","fps","durationS","width","height","ready","layerReady","calibrationQuality","calibrationRmsPx"]}}},"required":["matchId","analysisId","generation","demoAliases","ready","index","cameras"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For people who attended and match managers; 403 watchNotAllowed for other people in the match, 404 outside it. Playback reads saved results and never starts processing. index is null and ready false until the worker has committed a replay. Camera ids are recording ids within this analysis. Names are read separately from GET /overlay, which applies player visibility. Coordinates are pitch metres in the calibration's fixed orientation, never mirrored at half-time."}},"/api/v1/matches/{id}/replay/{name}":{"get":{"operationId":"getMatchesByIdReplayByName","summary":"A saved top-down replay file","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"name","in":"path","required":true,"schema":{"type":"string"}},{"name":"generation","in":"query","required":true,"description":"Committed run from GET /replay; must match /overlay.generation before drawing names","schema":{"type":"string"}}],"responses":{"200":{"description":"Raw JSON file (no data envelope)","content":{"application/json":{"schema":{"oneOf":[{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"version":{"type":"number","const":1},"fps":{"type":"number","exclusiveMinimum":0},"stride":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"start_frame":{"type":"integer","minimum":0,"maximum":9007199254740991},"end_frame":{"type":"integer","minimum":0,"maximum":9007199254740991},"chunk_frames":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunks":{"type":"array","items":{"type":"integer","minimum":0,"maximum":9007199254740991}},"pitch":{"type":"object","properties":{"length_m":{"type":"number","exclusiveMinimum":0},"width_m":{"type":"number","exclusiveMinimum":0},"measured":{"type":"boolean"}},"required":["length_m","width_m","measured"],"additionalProperties":false}},"required":["version","fps","stride","start_frame","end_frame","chunk_frames","chunks","pitch"],"additionalProperties":false},{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"frames":{"type":"object","propertyNames":{"type":"string","pattern":"^\\d+$"},"additionalProperties":{"type":"object","properties":{"p":{"type":"array","items":{"type":"array","prefixItems":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"number"},{"type":"number"}],"items":false,"minItems":3,"maxItems":3}},"b":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"anyOf":[{"type":"number","const":0},{"type":"number","const":1}]}],"items":false,"minItems":3,"maxItems":3}},"required":["p"],"additionalProperties":false}}},"required":["frames"],"additionalProperties":false}]}}}},"304":{"description":"Unchanged since the supplied If-None-Match ETag"},"4XX":{"description":"Unavailable or not permitted"}},"description":"For people who attended and match managers; 403 watchNotAllowed for other people in the match, 404 outside it. Playback reads saved results and never starts processing. name is index.json or chunk_<n>.json from index.chunks. Master frames use index.fps; p entries are [globalTrackOrdinal,xMetres,yMetres], b is [xMetres,yMetres,interpolated0or1] when seen. Missing observations stay missing. Use /overlay for current names and hidden track ids. Private ETag revalidation; 404 notReady for missing files. A missing or stale generation returns 409 replayChanged: refresh metadata and identity mapping together."}},"/api/v1/matches/{id}/cameras/{cameraId}/watch":{"get":{"operationId":"getMatchesByIdCamerasByCameraIdWatch","summary":"Watch one synchronized camera recording","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"cameraId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"url":{"type":"string","format":"uri"},"expiresAt":{"type":"string"},"durationS":{"type":["number","null"]},"width":{"type":["number","null"]},"height":{"type":["number","null"]}},"required":["url","expiresAt","durationS","width","height"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For people who attended and match managers; 403 watchNotAllowed for other people in the match, 404 outside it. Playback reads saved results and never starts processing. Returns a two-hour presigned link and records its issue in the audit log. 404 when the camera is not part of this match's analysis."}},"/api/v1/matches/{id}/cameras/{cameraId}/layer/{name}":{"get":{"operationId":"getMatchesByIdCamerasByCameraIdLayerByName","summary":"One camera's saved position layer","tags":["Video"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"cameraId","in":"path","required":true,"schema":{"type":"string"}},{"name":"name","in":"path","required":true,"schema":{"type":"string"}},{"name":"generation","in":"query","required":true,"description":"Committed run from the camera metadata; stale/missing returns 409 replayChanged","schema":{"type":"string"}}],"responses":{"200":{"description":"Raw JSON file (no data envelope)","content":{"application/json":{"schema":{"oneOf":[{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"fps":{"type":"number","exclusiveMinimum":0},"stride":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"start_frame":{"type":"integer","minimum":0,"maximum":9007199254740991},"end_frame":{"type":"integer","minimum":0,"maximum":9007199254740991},"width":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"height":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunk_frames":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"chunks":{"type":"array","items":{"type":"integer","minimum":0,"maximum":9007199254740991}},"teams":{"type":"object","propertyNames":{"type":"string","pattern":"^\\d+$"},"additionalProperties":{"anyOf":[{"type":"number","const":0},{"type":"number","const":1}]}},"team_colours":{"type":"object","properties":{"0":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"1":{"type":"string","pattern":"^#[0-9a-f]{6}$"}},"required":["0","1"],"additionalProperties":false},"keepers":{"type":"object","propertyNames":{"type":"string","pattern":"^\\d+$"},"additionalProperties":{"type":"string","enum":["near","far"]}},"spells":{"type":"array","items":{"type":"array","prefixItems":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"anyOf":[{"type":"number","const":-1},{"type":"number","const":0},{"type":"number","const":1}]},{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"integer","minimum":0,"maximum":9007199254740991}],"items":false,"minItems":4,"maxItems":4}},"passes":{"type":"array","items":{"type":"object","properties":{"start":{"type":"integer","minimum":0,"maximum":9007199254740991},"end":{"type":"integer","minimum":0,"maximum":9007199254740991},"passer":{"type":"integer","minimum":0,"maximum":9007199254740991},"receiver":{"type":"integer","minimum":0,"maximum":9007199254740991},"team":{"anyOf":[{"type":"number","const":-1},{"type":"number","const":0},{"type":"number","const":1}]},"completed":{"type":["boolean","null"]},"length_m":{"type":"number","minimum":0},"from":{"anyOf":[{"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2},{"type":"null"}]},"to":{"anyOf":[{"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2},{"type":"null"}]}},"required":["start","end","passer","receiver","team","completed","length_m","from","to"],"additionalProperties":false}},"goals":{"type":"array","items":{"type":"object","properties":{"frame":{"type":"integer","minimum":0,"maximum":9007199254740991},"goal":{"type":"string","enum":["near","far"]},"score":{"type":"number"},"evidence":{"type":"array","items":{"type":"string"}},"scorer":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"scorer_team":{"anyOf":[{"anyOf":[{"type":"number","const":-1},{"type":"number","const":0},{"type":"number","const":1}]},{"type":"null"}]},"assist":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"own_goal":{"type":"boolean"},"deflected":{"type":"boolean"},"at":{"anyOf":[{"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2},{"type":"null"}]}},"required":["frame","goal","score","evidence","scorer","scorer_team","assist","own_goal","deflected","at"],"additionalProperties":false}}},"required":["fps","stride","start_frame","end_frame","width","height","chunk_frames","chunks","teams","team_colours","keepers","spells","passes","goals"],"additionalProperties":false},{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"frames":{"type":"object","propertyNames":{"type":"string","pattern":"^\\d+$"},"additionalProperties":{"type":"object","properties":{"p":{"type":"array","items":{"type":"array","prefixItems":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}],"items":false,"minItems":5,"maxItems":5}},"b":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"anyOf":[{"type":"number","const":0},{"type":"number","const":1}]}],"items":false,"minItems":3,"maxItems":3},"o":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["p"],"additionalProperties":false}}},"required":["frames"],"additionalProperties":false}]}}}},"304":{"description":"Unchanged since the supplied If-None-Match ETag"},"4XX":{"description":"Unavailable or not permitted"}},"description":"For people who attended and match managers; 403 watchNotAllowed for other people in the match, 404 outside it. Playback reads saved results and never starts processing. name is index.json or chunk_<n>.json. Uses the LayerIndex/LayerChunk format and camera-local frames, but track ids are the analysis's global ordinals, shared with /overlay and /replay. Private ETag revalidation."}},"/api/v1/matches/{id}/prep":{"get":{"operationId":"getMatchesByIdPrep","summary":"What marking the pitch needs","tags":["Prep"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"matchId":{"type":"string"},"matchCancelled":{"type":"boolean"},"analysis":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},"updatedAt":{"type":"string"}},"required":["id","status","updatedAt"]},{"type":"null"}]},"ready":{"type":"boolean","description":"The median frame exists, so the pitch can be marked"},"image":{"anyOf":[{"type":"object","properties":{"url":{"type":"string","description":"A presigned GET link to median.jpg, valid for 2 hours"},"width":{"type":"number"},"height":{"type":"number"},"expiresAt":{"type":"string"}},"required":["url","width","height","expiresAt"]},{"type":"null"}],"description":"The empty-pitch frame clicks are made on; click coordinates are its pixels"},"video":{"anyOf":[{"type":"object","properties":{"url":{"type":"string","description":"A presigned GET link to the 720p copy (range requests work)"},"durationS":{"type":["number","null"]},"fps":{"type":["number","null"]},"expiresAt":{"type":"string"}},"required":["url","durationS","fps","expiresAt"]},{"type":"null"}],"description":"For finding kick-off, half-time and the end"},"pitch":{"type":"object","properties":{"lengthM":{"type":"number"},"widthM":{"type":"number"},"goalWidthM":{"type":"number"},"measured":{"type":"boolean"},"penaltyArea":{"anyOf":[{"type":"object","properties":{"widthM":{"type":"number"},"depthM":{"type":"number"}},"required":["widthM","depthM"]},{"type":"null"}]},"source":{"type":"string","enum":["analysis","pitch","camera","default"],"description":"Where the sizes come from: this match's saved prep, the venue's pitch, the camera's last calibration, or 40 x 25 m defaults"},"pitchName":{"type":["string","null"]}},"required":["lengthM","widthM","goalWidthM","measured","penaltyArea","source","pitchName"]},"landmarks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["corner_0_0","corner_L_0","corner_L_W","corner_0_W","halfway_0","halfway_W","centre_spot","goal_0_post_a","goal_0_post_b","goal_L_post_a","goal_L_post_b","box_0_line_a","box_0_front_a","box_0_front_b","box_0_line_b","box_L_line_a","box_L_front_a","box_L_front_b","box_L_line_b"]},"pitch":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2,"description":"Metres: x along the length from the left goal, y across from the far touchline"}},"required":["id","pitch"]}},"camera":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"official":{"type":"boolean"}},"required":["id","label","official"]},{"type":"null"}],"description":"The camera a save writes to; null until a pitch without one gets its first"},"prefill":{"anyOf":[{"type":"object","properties":{"source":{"type":"string","enum":["analysis","camera"],"description":"\"analysis\": this match was marked already; \"camera\": the camera's last calibration, so ask whether the lines still fit"},"calibrationId":{"type":"string"},"clicks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["corner_0_0","corner_L_0","corner_L_W","corner_0_W","halfway_0","halfway_W","centre_spot","goal_0_post_a","goal_0_post_b","goal_L_post_a","goal_L_post_b","box_0_line_a","box_0_front_a","box_0_front_b","box_0_line_b","box_L_line_a","box_L_front_a","box_L_front_b","box_L_line_b"]},"image":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2}},"required":["id","image"]}},"lambda":{"type":"number"},"rmsPx":{"description":"How far off these clicks are on this frame with the pitch sizes above and the saved lens term; null when they give no fit","type":["number","null"]},"quality":{"anyOf":[{"type":"string","enum":["GOOD","WEAK","REJECT"]},{"type":"null"}],"description":"The same fit's gate: ask whether the lines still fit only for GOOD or WEAK; otherwise let the person mark the pitch, starting from these clicks"},"savedAt":{"type":"string"},"cameraLabel":{"type":["string","null"]}},"required":["source","calibrationId","clicks","lambda","rmsPx","quality","savedAt","cameraLabel"]},{"type":"null"}],"description":"Clicks to start from; none from a camera calibrated on a video of another shape"},"window":{"anyOf":[{"type":"object","properties":{"kickoffS":{"type":"number"},"halftimeS":{"type":["number","null"]},"endS":{"type":"number"},"aDefends":{"type":"string","enum":["near","far"]},"switchedEnds":{"type":"boolean"}},"required":["kickoffS","halftimeS","endS","aDefends","switchedEnds"]},{"type":"null"}],"description":"The saved match window, if any"},"areas":{"type":"array","items":{"type":"array","items":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2}},"description":"The venue's areas to ignore on this frame (pixels of `image`), from its camera set-up: draw them read-only. Saving carries them into the match's calibration; the worker drops people outside the lines and ball runs that stay inside them"},"needsScore":{"type":"boolean","description":"The match has no score yet: marking the pitch waits for it (a venue may deliver the recording first). `can.save` and `can.redo` are false until then"},"can":{"type":"object","properties":{"save":{"type":"boolean","description":"The analysis waits for prep (NEEDS_PREP) and the score is in"},"redo":{"type":"boolean","description":"Tracking started already: saving again needs redo: true and starts tracking over"}},"required":["save","redo"]}},"required":["matchId","matchCancelled","analysis","ready","image","video","pitch","landmarks","camera","prefill","window","areas","needsScore","can"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For the organiser, side captains and admins (403 FORBIDDEN with fields.form prepNotAllowed for other people in the match, 404 outside it). Every call issues new links and writes them to the audit log.\n\nLandmark ids: corner_0_0 (top-left on the frame), corner_L_0, corner_L_W, corner_0_W, halfway_0, halfway_W, centre_spot, goal_0_post_a/b, goal_L_post_a/b, and with a penalty area box_0_* and box_L_*. x = 0 is the left goal, y = 0 the far touchline.\n\nWith `prefill.source` camera and `prefill.quality` GOOD or WEAK, show the camera's lines over the new frame and ask whether they still fit: if so, POST the prefilled clicks and lambda as they are (one tap).\n\nLinks are issued only while the pitch can be marked (`can.save` or `can.redo`); otherwise `image` and `video` are null."},"post":{"operationId":"postMatchesByIdPrep","summary":"Save the pitch marking and start tracking","tags":["Prep"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"clicks":{"maxItems":19,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["corner_0_0","corner_L_0","corner_L_W","corner_0_W","halfway_0","halfway_W","centre_spot","goal_0_post_a","goal_0_post_b","goal_L_post_a","goal_L_post_b","box_0_line_a","box_0_front_a","box_0_front_b","box_0_line_b","box_L_line_a","box_L_front_a","box_L_front_b","box_L_line_b"],"description":"The landmark, e.g. corner_0_0 (x = 0 is the left goal on the frame, y = 0 the far touchline)"},"image":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2,"description":"Where it is on the median frame, in its pixels: [x, y] from the top-left corner"}},"required":["id","image"]},"description":"At least 6 different landmarks, in both halves of the pitch"},"lambda":{"description":"The lens term; leave it out to search the best one (the browser preview does the same)","anyOf":[{"type":"number","minimum":-0.6,"maximum":0.6},{"type":"null"}]},"pitch":{"type":"object","properties":{"lengthM":{"type":"number","description":"Goal line to goal line, 15-110 m"},"widthM":{"type":"number","description":"Touchline to touchline, 10-75 m, not more than the length"},"goalWidthM":{"type":"number","description":"Between the posts, 1-7.32 m"},"measured":{"default":false,"description":"The sizes were tape-measured","type":"boolean"},"penaltyArea":{"default":null,"description":"Only when the pitch has penalty areas marked; enables the box_* landmarks","anyOf":[{"type":"object","properties":{"widthM":{"type":"number","description":"Across the pitch; wider than the goal"},"depthM":{"type":"number","description":"From the goal line"}},"required":["widthM","depthM"]},{"type":"null"}]}},"required":["lengthM","widthM","goalWidthM"]},"window":{"type":"object","properties":{"kickoffS":{"type":"number","description":"Kick-off, in seconds of the video"},"halftimeS":{"default":null,"description":"Start of the break; null when there was none","type":["number","null"]},"endS":{"type":"number","description":"The final whistle, in seconds of the video"},"aDefends":{"type":"string","enum":["near","far"],"description":"The goal side A defends in the first half: \"near\" is x = 0 (the left goal on the frame), \"far\" is x = length"},"switchedEnds":{"default":false,"description":"The sides switched ends at half-time (needs halftimeS)","type":"boolean"}},"required":["kickoffS","endS","aDefends"]},"redo":{"description":"Mark the pitch again after tracking started: cancels the running stage and tracks again","type":"boolean"}},"required":["clicks","pitch","window"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"analysisId":{"type":"string"},"status":{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"],"description":"TRACKING"},"calibrationId":{"type":"string"},"cameraId":{"type":["string","null"]},"jobId":{"type":"string"},"rmsPx":{"type":"number","description":"Reprojection error in 1080p-equivalent pixels"},"quality":{"type":"string","enum":["GOOD","WEAK"],"description":"GOOD up to 2 px; WEAK up to 4 px (heatmaps only)"},"lambda":{"type":"number"},"redo":{"type":"boolean"}},"required":["analysisId","status","calibrationId","cameraId","jobId","rmsPx","quality","lambda","redo"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"The server fits the camera again (the same code as the web preview) and, in one transaction, stores the calibration on the match's camera (a pitch without one gets \"Kamera 1\"), makes it the camera's active calibration, stores the window, moves the analysis to TRACKING and queues tracking.\n\n422 INVALID with fields.clicks: tooFew (fewer than 6 different landmarks), collinear, oneHalf (all in one half), noFit, rejected (more than 4 px off on average), unknownLandmark (box_* needs a penalty area), duplicate or outsideImage; fields like pitch.widthM (outOfRange, widerThanLong, goalTooWide, penaltyWidth, penaltyDepth) and window.endS (required, outOfVideo, beforeKickoff, outsideMatch).\n\n409 CONFLICT with fields.form scoreFirst (enter the score first: labelling and stats need the roster and the goals), noVideo, notReady (still ingesting), matchCancelled, alreadyPrepared (tracking started; send redo: true to start over, which cancels the running stage) or alreadyQueued.\n\nFor an official-camera match whose camera the venue set up, the venue's calibration is the one-tap prefill, and its areas to ignore move into this match's frame through both fits."}},"/api/v1/matches/{id}/tracklets":{"get":{"operationId":"getMatchesByIdTracklets","summary":"Who is who: the roster and the tracked people","tags":["Kim kim?"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"matchId":{"type":"string"},"analysisId":{"type":["string","null"]},"status":{"anyOf":[{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},{"type":"null"}]},"ready":{"type":"boolean","description":"The tracker's results are in; false before tracking finished (tracklets is empty)"},"sides":{"type":"object","properties":{"A":{"type":"string"},"B":{"type":"string"}},"required":["A","B"],"description":"The sides' labels"},"video":{"type":"object","properties":{"fps":{"type":["number","null"]},"frameCount":{"type":["number","null"]},"width":{"type":["number","null"]},"height":{"type":["number","null"]},"durationS":{"type":["number","null"]},"ready":{"type":"boolean","description":"GET /video gives a link to the proxy"}},"required":["fps","frameCount","width","height","durationS","ready"]},"can":{"type":"object","properties":{"manage":{"type":"boolean"},"label":{"type":"boolean","description":"May change labels now"}},"required":["manage","label"]},"version":{"type":"number","description":"The number of decisions so far"},"changedSincePublish":{"type":"boolean","description":"Labels, or the score card (who played, on which side, the goals), changed after the stats were worked out: finish again to update them. A score save clears labels that name someone who no longer played"},"rosterFromRsvp":{"type":"boolean","description":"Attendance was not entered; the roster is the people who said yes"},"safeMargin":{"type":"number","description":"The margin accept-suggestions uses by default"},"roster":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]},"kind":{"type":"string","enum":["registered","guest"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"keeper":{"type":"boolean","description":"Plays in goal (positions include goalkeeper)"}},"required":["playerId","displayName","handle","kind","side","keeper"]},"description":"The players who attended: side A, side B, then without a side"},"tracklets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"ordinal":{"type":"number","description":"The tracker's id: the track id in the video layer's boxes"},"startFrame":{"type":"number","description":"Frames of the proxy video: frame = round(mediaTime × video.fps)"},"endFrame":{"type":"number","description":"Inclusive"},"sideGuess":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side its shirt colour matched"},"sideConf":{"type":["number","null"]},"keeperGuess":{"type":"boolean"},"insideRatio":{"type":"number","description":"Share of the time inside the pitch"},"label":{"type":"string","enum":["unlabeled","player","not_player","mixed"]},"playerId":{"type":["string","null"]},"source":{"description":"\"user\" or \"suggestion\" once labelled","type":["string","null"]},"suggestion":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number","description":"The player's probability, 0..1 (shown as \"Ali? %82\")"},"margin":{"type":"number","description":"p minus the runner-up's probability (another player or nobody on the roster)"}},"required":["playerId","p","margin"]},{"type":"null"}],"description":"For people who may label, on unlabelled tracklets"},"strip":{"type":"boolean","description":"A crop strip exists: GET /tracklets/{trackletId}/strip"}},"required":["id","ordinal","startFrame","endFrame","sideGuess","sideConf","keeperGuess","insideRatio","label","playerId","source","suggestion","strip"]}},"matchWindow":{"anyOf":[{"type":"object","properties":{"startFrame":{"type":"number"},"endFrame":{"type":"number"},"halftimeFrame":{"type":["number","null"]}},"required":["startFrame","endFrame","halftimeFrame"]},{"type":"null"}],"description":"Kick-off to the final whistle in frames (the span of the per-player timelines); null before tracking"},"rollCall":{"type":"array","items":{"type":"object","properties":{"period":{"anyOf":[{"type":"number","const":1},{"type":"number","const":2}],"description":"1 after kick-off; 2 after half-time, only when the sides switched ends"},"frame":{"type":"number","description":"Show this frame paused: seek to (frame + 0.5) / video.fps"},"width":{"type":"number"},"height":{"type":"number"},"people":{"type":"array","items":{"type":"object","properties":{"number":{"type":"number","description":"The box's number on the frame, 1… from left to right"},"trackletId":{"type":"string"},"ordinal":{"type":"number"},"box":{"type":"array","prefixItems":[{"type":"number"},{"type":"number"},{"type":"number"},{"type":"number"}],"items":false,"minItems":4,"maxItems":4,"description":"[x0, y0, x1, y1] in the layer's pixel space (width x height)"}},"required":["number","trackletId","ordinal","box"]},"description":"Every box on the frame"}},"required":["period","frame","width","height","people"]},"description":"Roll call (Yoklama), for people who may label: a frame soon after kick-off where the number of people on the pitch is closest to the number of players and they stand well apart (and one after half-time when the ends were switched). Label each box's tracklet with POST /labels; continuity then carries the labels on to the suggestions. Empty without a video layer"}},"required":["matchId","analysisId","status","ready","sides","video","can","version","changedSincePublish","rosterFromRsvp","safeMargin","roster","tracklets","matchWindow","rollCall"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For people who attended and the organiser, side captains and admins (403 watchNotAllowed for others in the match, 404 for everyone else). Every tracklet is one person the tracker followed for a while. Draw them over the video with the layer files (GET /layer/index.json, then /layer/chunk_<n>.json): a box's track id is the tracklet's ordinal. A fast way to label: the roll call first (rollCall), then accept the safe suggestions, then review the rest one at a time, lowest margin first (the order the app's Kontrol step uses: margin, then p, then the longer tracklet, then the earlier one)."}},"/api/v1/matches/{id}/tracklets/{trackletId}/strip":{"get":{"operationId":"getMatchesByIdTrackletsByTrackletIdStrip","summary":"A tracklet's crop strip","tags":["Kim kim?"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"trackletId","in":"path","required":true,"schema":{"type":"string"}},{"name":"load","in":"query","required":false,"description":"Any id (8-64 letters, digits, - or _) shared by every strip one screen shows: access is audited once per load (without it, once every two hours)","schema":{"type":"string"}}],"responses":{"200":{"description":"The strip","content":{"image/webp":{"schema":{"type":"string","format":"binary"}}}},"304":{"description":"Not modified: the strip is the one with the ETag sent in If-None-Match"},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object"}}}}},"description":"For people who attended and the organiser, side captains and admins (403 watchNotAllowed for others in the match, 404 for everyone else). A 240 x 64 WebP: five 48 x 64 crops of the person over the tracklet's life, oldest first, for telling people apart. Strips are pictures of people: the answer may be kept by the viewer's own client (private, one hour) but never by a shared cache, and other sites may not embed it (Cross-Origin-Resource-Policy: same-origin); send If-None-Match with the ETag to get 304 while unchanged. 404 noStrip when the tracker left none (tracklet.strip is false), 404 trackletNotFound for a tracklet of another match."}},"/api/v1/matches/{id}/labels":{"post":{"operationId":"postMatchesByIdLabels","summary":"Label a tracklet","tags":["Kim kim?"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"trackletId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"label":{"type":"string","enum":["player","not_player","mixed","unlabeled"]},"playerId":{"description":"The player for label player; with label unlabeled, a player this tracklet is not (rejects that suggestion)","anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"source":{"description":"\"suggestion\" when the person accepted the suggestion shown for this tracklet","type":"string","enum":["user","suggestion"]},"onlyIfUnlabeled":{"description":"Refuse a conflicting existing label instead of replacing it; an identical label is still a no-op","type":"boolean"}},"required":["trackletId","label"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"tracklet":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string","enum":["unlabeled","player","not_player","mixed"]},"playerId":{"type":["string","null"]},"source":{"type":["string","null"]}},"required":["id","label","playerId","source"]},"previousVersion":{"type":"number","description":"The version this change started from; when it differs from yours, someone else labelled too: GET /tracklets again"},"version":{"type":"number"},"suggestions":{"type":"array","items":{"type":"object","properties":{"trackletId":{"type":"string"},"suggestion":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number","description":"The player's probability, 0..1 (shown as \"Ali? %82\")"},"margin":{"type":"number","description":"p minus the runner-up's probability (another player or nobody on the roster)"}},"required":["playerId","p","margin"]},{"type":"null"}],"description":"Null where the suggestion went away"}},"required":["trackletId","suggestion"]},"description":"The suggestions that changed, and always the labelled tracklet's own (null when it has none)"}},"required":["tracklet","previousVersion","version","suggestions"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For the organiser, side captains and admins (403 labelNotAllowed for others in the match, 404 for everyone else). Labels change while the analysis is NEEDS_LABELS or PUBLISHED (then finish again to update the stats), or FAILED in the stats stage (failedStage stats: fix labels, then finish again); 409 labelsLocked while the stats are worked out or after another stage failed, 409 notReady before tracking finished.\n\nlabel player needs playerId, a player who attended (422 notInRoster otherwise); not_player is a referee, coach or spectator; mixed is two people merged into one track; unlabeled clears the label. With unlabeled and a playerId (rejecting a suggestion) the tracklet is unlabelled afterwards, its label cleared if it had one, and that player is no longer suggested for it until its label changes.\n\nEvery change appends a decision to the tracklet's history; the same label again changes nothing. The answer carries the suggestions that changed and the version it started from."}},"/api/v1/matches/{id}/labels/accept-suggestions":{"post":{"operationId":"postMatchesByIdLabelsAcceptSuggestions","summary":"Accept every safe suggestion","tags":["Kim kim?"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"minMargin":{"description":"Accept suggestions with at least this margin; default 0.5","type":"number","minimum":0.3,"maximum":1}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"accepted":{"type":"number"},"tracklets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string","enum":["unlabeled","player","not_player","mixed"]},"playerId":{"type":["string","null"]},"source":{"type":["string","null"]}},"required":["id","label","playerId","source"]}},"previousVersion":{"type":"number"},"version":{"type":"number"},"suggestions":{"type":"array","items":{"type":"object","properties":{"trackletId":{"type":"string"},"suggestion":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number","description":"The player's probability, 0..1 (shown as \"Ali? %82\")"},"margin":{"type":"number","description":"p minus the runner-up's probability (another player or nobody on the roster)"}},"required":["playerId","p","margin"]},{"type":"null"}],"description":"Null where the suggestion went away"}},"required":["trackletId","suggestion"]}}},"required":["accepted","tracklets","previousVersion","version","suggestions"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For the organiser, side captains and admins (403 labelNotAllowed for others in the match, 404 for everyone else). Labels change while the analysis is NEEDS_LABELS or PUBLISHED (then finish again to update the stats), or FAILED in the stats stage (failedStage stats: fix labels, then finish again); 409 labelsLocked while the stats are worked out or after another stage failed, 409 notReady before tracking finished. Labels each tracklet whose suggestion has at least minMargin (default 0.5) as that player, one decision each, all in one step. The body may be left out."}},"/api/v1/matches/{id}/labels/finish":{"post":{"operationId":"postMatchesByIdLabelsFinish","summary":"Finish labelling and work out the stats","tags":["Kim kim?"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"allowMissing":{"description":"Go ahead although some attended players have no tracklet","type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"analysisId":{"type":"string"},"status":{"type":"string","const":"COMPUTING"},"jobId":{"type":"string"},"labelledPlayers":{"type":"number"},"missingPlayers":{"type":"number","description":"Attended players without a tracklet (allowed with allowMissing)"}},"required":["analysisId","status","jobId","labelledPlayers","missingPlayers"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"For the organiser, side captains and admins (403 labelNotAllowed for others in the match, 404 for everyone else). Labels change while the analysis is NEEDS_LABELS or PUBLISHED (then finish again to update the stats), or FAILED in the stats stage (failedStage stats: fix labels, then finish again); 409 labelsLocked while the stats are worked out or after another stage failed, 409 notReady before tracking finished. Moves the analysis to COMPUTING and queues the stats job in one step; poll GET /analysis until PUBLISHED. 409 nothingLabelled when no tracklet is a player yet; 409 playersMissing when attended players have no tracklet (the roster entries without one): send allowMissing true to go ahead. The body may be left out."}},"/api/v1/matches/{id}/layer/{name}":{"get":{"operationId":"getMatchesByIdLayerByName","summary":"A video layer file","tags":["Kim kim?"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"name","in":"path","required":true,"schema":{"type":"string"}},{"name":"generation","in":"query","required":false,"description":"Optional committed worker run from GET /replay. When supplied, reads the immutable layer for that run and returns 409 replayChanged if stale; use with an overlay bearing the same generation.","schema":{"type":"string"}}],"responses":{"200":{"description":"The layer file","content":{"application/json":{"schema":{"type":"object"}}}},"304":{"description":"Not modified: the file is the one with the ETag sent in If-None-Match"},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object"}}}}},"description":"For people who attended and the organiser, side captains and admins (403 watchNotAllowed for others in the match, 404 for everyone else). name is index.json or chunk_<n>.json; the format is LayerIndex and LayerChunk in packages/contracts/src/pipeline.ts: index.json gives fps, stride, width and height (the boxes' pixel space) and the chunk numbers; chunk n holds frames [n × chunk_frames, (n + 1) × chunk_frames) as { frames: { \"<frame>\": { p: [[trackId, x0, y0, x1, y1], …], b?: [x, y, interpolated], o?: trackId } } }. The JSON is returned as is (no { data } envelope). The files are rewritten when the pitch is marked again, so keep them with their ETag and send If-None-Match: 304 while unchanged. 404 notReady before tracking finished or for a chunk that does not exist."}},"/api/v1/matches/{id}/report":{"get":{"operationId":"getMatchesByIdReport","summary":"The match report","tags":["Report"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"ready":{"type":"boolean","const":false},"matchId":{"type":"string"},"matchStatus":{"type":"string","enum":["planned","played","cancelled"]},"scoreState":{"type":"string","enum":["none","entered","confirmed"]},"recording":{"type":"string","enum":["upload","official"]},"analysis":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},"progress":{"type":"number","description":"0..1 within the current stage"}},"required":["id","status","progress"]},{"type":"null"}],"description":"Null until a video came in"},"can":{"type":"object","properties":{"manage":{"type":"boolean"},"upload":{"type":"boolean","description":"May upload the match video"}},"required":["manage","upload"]}},"required":["ready","matchId","matchStatus","scoreState","recording","analysis","can"],"description":"Before the analysis is published: where the match video stands (GET /api/v1/matches/{id}/analysis has the details)"},{"type":"object","properties":{"ready":{"type":"boolean","const":true},"matchId":{"type":"string"},"analysisId":{"type":"string"},"publishedAt":{"type":["string","null"]},"kickoffS":{"type":"number","description":"Kick-off in seconds into the video"},"pitch":{"type":"object","properties":{"length":{"type":"number"},"width":{"type":"number"}},"required":["length","width"],"description":"Metres"},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"label":{"type":"string"},"goals":{"description":"The entered score; null until then","type":["number","null"]}},"required":["label","goals"]},"B":{"type":"object","properties":{"label":{"type":"string"},"goals":{"type":["number","null"]}},"required":["label","goals"]}},"required":["A","B"]},"can":{"type":"object","properties":{"manage":{"type":"boolean","description":"May confirm goals"},"watch":{"type":"boolean"}},"required":["manage","watch"]},"video":{"type":"object","properties":{"ready":{"type":"boolean"},"durationS":{"type":["number","null"]},"width":{"type":["number","null"]},"height":{"type":["number","null"]}},"required":["ready","durationS","width","height"]},"highlights":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["goal","chance","save"]},"startS":{"type":"number","description":"Play the match video from here …"},"endS":{"type":"number","description":"… to here (seconds)"},"minute":{"type":"number"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"goal":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["candidate","confirmed"]},"ownGoal":{"type":"boolean"},"scorer":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"assist":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]}},"required":["id","status","ownGoal","scorer","assist"]},{"type":"null"}],"description":"Goal highlights: scorer and assist once a manager confirmed the goal; rejected candidates are left out"}},"required":["id","kind","startS","endS","minute","side","goal"]}},"goals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["candidate","confirmed","rejected"]},"source":{"type":"string","enum":["auto","user"],"description":"auto: found in the video; user: added by hand"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side credited with the goal (an own goal counts for the side that did not put it in)"},"atS":{"type":"number","description":"Seconds into the match video"},"minute":{"type":"number","description":"Match minute from kick-off: 0:00–0:59 is the 1st"},"clip":{"type":"object","properties":{"startS":{"type":"number"},"endS":{"type":"number"}},"required":["startS","endS"],"description":"The stretch of video to play: 8 s before the goal to 4 s after"},"ownGoal":{"type":"boolean"},"scorer":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}],"description":"For candidates the analysis's best guess; for decided goals who scored (null when nobody was listed on that side)"},"assist":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"confidence":{"description":"The analysis's confidence in a candidate, 0..1","type":["number","null"]},"options":{"type":"object","properties":{"scorer":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}},"assist":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}}},"required":["scorer","assist"],"description":"Everyone the analysis considered, best first"},"decidedBy":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"decidedAt":{"type":["string","null"]}},"required":["id","status","source","side","atS","minute","clip","ownGoal","scorer","assist","confidence","options","decidedBy","decidedAt"]},"description":"Confirmed goals in match order"},"review":{"type":"object","properties":{"candidates":{"type":"number","description":"Goal candidates waiting for a manager's decision"},"tally":{"type":"object","properties":{"A":{"type":"number"},"B":{"type":"number"}},"required":["A","B"],"description":"Confirmed goals per side"},"mismatch":{"type":"boolean","description":"The score is in and the confirmed goals do not add up to it"}},"required":["candidates","tally","mismatch"]},"motm":{"type":"object","properties":{"totalVotes":{"type":"number"},"votes":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"votes":{"type":"number"}},"required":["playerId","displayName","side","votes"]}},"leaders":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"votes":{"type":"number"}},"required":["playerId","displayName","side","votes"]},"description":"The most voted; several on a tie"},"suggested":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"rating":{"type":"number"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["playerId","displayName","side","rating","photo"]},{"type":"null"}],"description":"The analysis's pick: the highest rating you may see"},"myVote":{"description":"The player you voted for, while that vote counts (votes are cast with POST /api/v1/matches/{id}/motm)","type":["string","null"]}},"required":["totalVotes","votes","leaders","suggested","myVote"]},"players":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"type":["string","null"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"isMe":{"type":"boolean"},"visible":{"type":"boolean","description":"False when the player's visibility setting hides their numbers from you; then every number is null"},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}],"description":"Their profile photo, under the same rule as the numbers"},"tier":{"anyOf":[{"type":"string","enum":["A","B","C"]},{"type":"null"}],"description":"A: full numbers; B (partial): minutes and the heatmap; C (not enough data): nothing"},"minutes":{"type":["number","null"]},"distanceM":{"description":"Estimated, tier A at a believable pace (40–140 m/min) only","type":["number","null"]},"metersPerMin":{"type":["number","null"]},"rating":{"description":"0–10, tier A","type":["number","null"]},"touches":{"type":["number","null"]},"passes":{"type":["number","null"]},"passesCompleted":{"type":["number","null"]},"completion":{"description":"Completed passes as a share of passes, 0..1","type":["number","null"]},"shots":{"type":["number","null"]},"goals":{"type":"number","description":"Confirmed goals; own goals count for nobody"},"assists":{"type":"number"},"avg":{"anyOf":[{"type":"object","properties":{"u":{"type":"number"},"v":{"type":"number"}},"required":["u","v"]},{"type":"null"}],"description":"Average position on a unit pitch, attacking left to right (tier A)"},"heatmap":{"anyOf":[{"type":"object","properties":{"cols":{"type":"number"},"rows":{"type":"number"},"grid":{"type":"array","items":{"type":"number"}}},"required":["cols","rows","grid"],"description":"Seconds per cell, row-major: grid[row * cols + col]. Columns run along the length from the subject's own goal to the goal it attacks (the subject attacks left to right); rows run across, from the far touchline. The ball grid uses side A's direction. Blur with sigma ≈ 1.5 m when drawing."},{"type":"null"}]}},"required":["playerId","displayName","handle","side","isMe","visible","photo","tier","minutes","distanceM","metersPerMin","rating","touches","passes","passesCompleted","completion","shots","goals","assists","avg","heatmap"]}},"heatmaps":{"type":"object","properties":{"sides":{"type":"object","properties":{"A":{"anyOf":[{"type":"object","properties":{"cols":{"type":"number"},"rows":{"type":"number"},"grid":{"type":"array","items":{"type":"number"}}},"required":["cols","rows","grid"],"description":"Seconds per cell, row-major: grid[row * cols + col]. Columns run along the length from the subject's own goal to the goal it attacks (the subject attacks left to right); rows run across, from the far touchline. The ball grid uses side A's direction. Blur with sigma ≈ 1.5 m when drawing."},{"type":"null"}]},"B":{"anyOf":[{"type":"object","properties":{"cols":{"type":"number"},"rows":{"type":"number"},"grid":{"type":"array","items":{"type":"number"}}},"required":["cols","rows","grid"],"description":"Seconds per cell, row-major: grid[row * cols + col]. Columns run along the length from the subject's own goal to the goal it attacks (the subject attacks left to right); rows run across, from the far touchline. The ball grid uses side A's direction. Blur with sigma ≈ 1.5 m when drawing."},{"type":"null"}]}},"required":["A","B"],"description":"Each side's grid is the sum of its players' grids, over the players whose numbers you may see (`visible`); null when there are none"},"ball":{"anyOf":[{"type":"object","properties":{"cols":{"type":"number"},"rows":{"type":"number"},"grid":{"type":"array","items":{"type":"number"}}},"required":["cols","rows","grid"],"description":"Seconds per cell, row-major: grid[row * cols + col]. Columns run along the length from the subject's own goal to the goal it attacks (the subject attacks left to right); rows run across, from the far touchline. The ball grid uses side A's direction. Blur with sigma ≈ 1.5 m when drawing."},{"type":"null"}]}},"required":["sides","ball"]},"totals":{"type":"object","properties":{"A":{"type":"object","properties":{"players":{"type":"number"},"passes":{"type":"number"},"passesCompleted":{"type":"number"},"completion":{"type":["number","null"]},"touches":{"type":"number"},"shots":{"type":"number"}},"required":["players","passes","passesCompleted","completion","touches","shots"],"description":"Over the side's players whose full numbers you see"},"B":{"type":"object","properties":{"players":{"type":"number"},"passes":{"type":"number"},"passesCompleted":{"type":"number"},"completion":{"type":["number","null"]},"touches":{"type":"number"},"shots":{"type":"number"}},"required":["players","passes","passesCompleted","completion","touches","shots"],"description":"Over the side's players whose full numbers you see"}},"required":["A","B"]}},"required":["ready","matchId","analysisId","publishedAt","kickoffS","pitch","sides","can","video","highlights","goals","review","motm","players","heatmaps","totals"]}]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Until the video analysis is published, `ready` is false and the body says where the video stands (anyone involved in the match).\n\nOnce published: highlights, confirmed goals, the man-of-the-match tally with the analysis's suggestion, heatmaps, passes and per-player numbers. For people who played (attended) and the match's managers. Others involved in the match get 403 FORBIDDEN with fields.form reportNotAllowed once the report is published; people outside the match get 404.\n\nEach player's numbers follow their visibility setting (`visible`) and their tier. Play the highlights with GET …/highlights."}},"/api/v1/matches/{id}/highlights":{"get":{"operationId":"getMatchesByIdHighlights","summary":"Highlights and a link to play them","tags":["Report"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"highlights":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["goal","chance","save"]},"startS":{"type":"number","description":"Play the match video from here …"},"endS":{"type":"number","description":"… to here (seconds)"},"minute":{"type":"number"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]},"goal":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["candidate","confirmed"]},"ownGoal":{"type":"boolean"},"scorer":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"assist":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]}},"required":["id","status","ownGoal","scorer","assist"]},{"type":"null"}],"description":"Goal highlights: scorer and assist once a manager confirmed the goal; rejected candidates are left out"}},"required":["id","kind","startS","endS","minute","side","goal"]}},"video":{"anyOf":[{"type":"object","properties":{"url":{"type":"string","description":"A presigned GET link to the 720p match video, valid for 2 hours; supports range requests"},"expiresAt":{"type":"string"},"durationS":{"type":["number","null"]},"width":{"type":["number","null"]},"height":{"type":["number","null"]}},"required":["url","expiresAt","durationS","width","height"]},{"type":"null"}]}},"required":["highlights","video"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Play each highlight from `startS` to `endS` of the linked video, one after another. Empty until the analysis is published. For people who played and managers (403 watchNotAllowed for others in the match, 404 outside it). Every link issued is written to the audit log; links last 2 hours."}},"/api/v1/matches/{id}/goals":{"get":{"operationId":"getMatchesByIdGoals","summary":"Goal candidates and goals","tags":["Report"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"ready":{"type":"boolean","const":false},"matchId":{"type":"string"},"matchStatus":{"type":"string","enum":["planned","played","cancelled"]},"scoreState":{"type":"string","enum":["none","entered","confirmed"]},"recording":{"type":"string","enum":["upload","official"]},"analysis":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},"progress":{"type":"number","description":"0..1 within the current stage"}},"required":["id","status","progress"]},{"type":"null"}],"description":"Null until a video came in"},"can":{"type":"object","properties":{"manage":{"type":"boolean"},"upload":{"type":"boolean","description":"May upload the match video"}},"required":["manage","upload"]}},"required":["ready","matchId","matchStatus","scoreState","recording","analysis","can"],"description":"Before the analysis is published: where the match video stands (GET /api/v1/matches/{id}/analysis has the details)"},{"type":"object","properties":{"ready":{"type":"boolean","const":true},"matchId":{"type":"string"},"analysisId":{"type":"string"},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"label":{"type":"string"},"goals":{"description":"The entered score; null until then","type":["number","null"]}},"required":["label","goals"]},"B":{"type":"object","properties":{"label":{"type":"string"},"goals":{"type":["number","null"]}},"required":["label","goals"]}},"required":["A","B"]},"kickoffS":{"type":"number"},"video":{"type":"object","properties":{"ready":{"type":"boolean"},"durationS":{"type":["number","null"]},"width":{"type":["number","null"]},"height":{"type":["number","null"]}},"required":["ready","durationS","width","height"]},"can":{"type":"object","properties":{"decide":{"type":"boolean","description":"Organiser, side captains and admins"},"watch":{"type":"boolean"}},"required":["decide","watch"]},"candidates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["candidate","confirmed","rejected"]},"source":{"type":"string","enum":["auto","user"],"description":"auto: found in the video; user: added by hand"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side credited with the goal (an own goal counts for the side that did not put it in)"},"atS":{"type":"number","description":"Seconds into the match video"},"minute":{"type":"number","description":"Match minute from kick-off: 0:00–0:59 is the 1st"},"clip":{"type":"object","properties":{"startS":{"type":"number"},"endS":{"type":"number"}},"required":["startS","endS"],"description":"The stretch of video to play: 8 s before the goal to 4 s after"},"ownGoal":{"type":"boolean"},"scorer":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}],"description":"For candidates the analysis's best guess; for decided goals who scored (null when nobody was listed on that side)"},"assist":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"confidence":{"description":"The analysis's confidence in a candidate, 0..1","type":["number","null"]},"options":{"type":"object","properties":{"scorer":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}},"assist":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}}},"required":["scorer","assist"],"description":"Everyone the analysis considered, best first"},"decidedBy":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"decidedAt":{"type":["string","null"]}},"required":["id","status","source","side","atS","minute","clip","ownGoal","scorer","assist","confidence","options","decidedBy","decidedAt"]}},"confirmed":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["candidate","confirmed","rejected"]},"source":{"type":"string","enum":["auto","user"],"description":"auto: found in the video; user: added by hand"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side credited with the goal (an own goal counts for the side that did not put it in)"},"atS":{"type":"number","description":"Seconds into the match video"},"minute":{"type":"number","description":"Match minute from kick-off: 0:00–0:59 is the 1st"},"clip":{"type":"object","properties":{"startS":{"type":"number"},"endS":{"type":"number"}},"required":["startS","endS"],"description":"The stretch of video to play: 8 s before the goal to 4 s after"},"ownGoal":{"type":"boolean"},"scorer":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}],"description":"For candidates the analysis's best guess; for decided goals who scored (null when nobody was listed on that side)"},"assist":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"confidence":{"description":"The analysis's confidence in a candidate, 0..1","type":["number","null"]},"options":{"type":"object","properties":{"scorer":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}},"assist":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}}},"required":["scorer","assist"],"description":"Everyone the analysis considered, best first"},"decidedBy":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"decidedAt":{"type":["string","null"]}},"required":["id","status","source","side","atS","minute","clip","ownGoal","scorer","assist","confidence","options","decidedBy","decidedAt"]}},"rejected":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["candidate","confirmed","rejected"]},"source":{"type":"string","enum":["auto","user"],"description":"auto: found in the video; user: added by hand"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side credited with the goal (an own goal counts for the side that did not put it in)"},"atS":{"type":"number","description":"Seconds into the match video"},"minute":{"type":"number","description":"Match minute from kick-off: 0:00–0:59 is the 1st"},"clip":{"type":"object","properties":{"startS":{"type":"number"},"endS":{"type":"number"}},"required":["startS","endS"],"description":"The stretch of video to play: 8 s before the goal to 4 s after"},"ownGoal":{"type":"boolean"},"scorer":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}],"description":"For candidates the analysis's best guess; for decided goals who scored (null when nobody was listed on that side)"},"assist":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"confidence":{"description":"The analysis's confidence in a candidate, 0..1","type":["number","null"]},"options":{"type":"object","properties":{"scorer":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}},"assist":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}}},"required":["scorer","assist"],"description":"Everyone the analysis considered, best first"},"decidedBy":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"decidedAt":{"type":["string","null"]}},"required":["id","status","source","side","atS","minute","clip","ownGoal","scorer","assist","confidence","options","decidedBy","decidedAt"]}},"roster":{"type":"object","properties":{"A":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]},"side":{"type":"string","enum":["A","B"]}},"required":["playerId","displayName","handle","side"]}},"B":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]},"side":{"type":"string","enum":["A","B"]}},"required":["playerId","displayName","handle","side"]}}},"required":["A","B"],"description":"Who can be credited on each side: people who played there and players the analysis identified there"},"tally":{"type":"object","properties":{"A":{"type":"number"},"B":{"type":"number"}},"required":["A","B"]},"mismatch":{"type":"boolean"}},"required":["ready","matchId","analysisId","sides","kickoffS","video","can","candidates","confirmed","rejected","roster","tally","mismatch"]}]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"The analysis leaves one candidate per goal of the score, with a best guess for the scorer and the assist. Managers confirm or reject them (POST …/goals/{goalId}) and add missed goals (POST here). Play a goal's `clip` from the match video (GET /api/v1/matches/{id}/video). For people who played (attended) and the match's managers. Others involved in the match get 403 FORBIDDEN with fields.form goalsNotAllowed once the report is published; people outside the match get 404."},"post":{"operationId":"postMatchesByIdGoals","summary":"Add a goal the video missed","tags":["Report"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"at":{"description":"When the ball went in, in the match video: seconds as a number, or what people type: \"mm:ss\" (\"23:15\"), \"h:mm:ss\", or whole minutes (\"23\")","type":["number","string"]},"side":{"type":"string","enum":["A","B"],"description":"The side credited with the goal"},"scorerId":{"description":"Must have played for the credited side (for an own goal: for the other side; see `roster`). Null only when nobody is listed on that side, e.g. an opponent team that is not on Akıllı Futbol: the goal then counts for the side and for nobody","type":["string","null"]},"assistId":{"description":"Played for that side; leave out or null for no assist","type":["string","null"]},"ownGoal":{"type":"boolean"}},"required":["at","side"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["candidate","confirmed","rejected"]},"source":{"type":"string","enum":["auto","user"],"description":"auto: found in the video; user: added by hand"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side credited with the goal (an own goal counts for the side that did not put it in)"},"atS":{"type":"number","description":"Seconds into the match video"},"minute":{"type":"number","description":"Match minute from kick-off: 0:00–0:59 is the 1st"},"clip":{"type":"object","properties":{"startS":{"type":"number"},"endS":{"type":"number"}},"required":["startS","endS"],"description":"The stretch of video to play: 8 s before the goal to 4 s after"},"ownGoal":{"type":"boolean"},"scorer":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}],"description":"For candidates the analysis's best guess; for decided goals who scored (null when nobody was listed on that side)"},"assist":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"confidence":{"description":"The analysis's confidence in a candidate, 0..1","type":["number","null"]},"options":{"type":"object","properties":{"scorer":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}},"assist":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}}},"required":["scorer","assist"],"description":"Everyone the analysis considered, best first"},"decidedBy":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"decidedAt":{"type":["string","null"]}},"required":["id","status","source","side","atS","minute","clip","ownGoal","scorer","assist","confidence","options","decidedBy","decidedAt"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Organiser, side captains and admins (403 decideNotAllowed). The goal is confirmed at once (source user) and gets a highlight. 409 notPublished until the analysis is published. 422 INVALID with fields: at (required, invalid, afterEnd: past the end of the video), side (required), scorerId (required, notOnSide, notOpponent for own goals), assistId (notOnSide, sameAsScorer, ownGoal)."}},"/api/v1/matches/{id}/goals/{goalId}":{"post":{"operationId":"postMatchesByIdGoalsByGoalId","summary":"Answer “Bu gol mü?”","tags":["Report"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"goalId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"decision":{"type":"string","enum":["confirm","reject"]},"side":{"description":"The side credited with the goal; left out: the candidate's side","type":"string","enum":["A","B"]},"scorerId":{"description":"Left out: the analysis's best guess. Must have played for the credited side (for an own goal: for the other side; see `roster`). Null only when nobody is listed on that side, e.g. an opponent team that is not on Akıllı Futbol: the goal then counts for the side and for nobody","type":["string","null"]},"assistId":{"description":"Left out: the analysis's best guess; null: no assist. Must have played for the credited side; none for own goals","type":["string","null"]},"ownGoal":{"type":"boolean"}},"required":["decision"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["candidate","confirmed","rejected"]},"source":{"type":"string","enum":["auto","user"],"description":"auto: found in the video; user: added by hand"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side credited with the goal (an own goal counts for the side that did not put it in)"},"atS":{"type":"number","description":"Seconds into the match video"},"minute":{"type":"number","description":"Match minute from kick-off: 0:00–0:59 is the 1st"},"clip":{"type":"object","properties":{"startS":{"type":"number"},"endS":{"type":"number"}},"required":["startS","endS"],"description":"The stretch of video to play: 8 s before the goal to 4 s after"},"ownGoal":{"type":"boolean"},"scorer":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}],"description":"For candidates the analysis's best guess; for decided goals who scored (null when nobody was listed on that side)"},"assist":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"confidence":{"description":"The analysis's confidence in a candidate, 0..1","type":["number","null"]},"options":{"type":"object","properties":{"scorer":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}},"assist":{"type":"array","items":{"type":"object","properties":{"playerId":{"type":"string"},"p":{"type":"number"}},"required":["playerId","p"]}}},"required":["scorer","assist"],"description":"Everyone the analysis considered, best first"},"decidedBy":{"anyOf":[{"type":"object","properties":{"playerId":{"type":"string"},"displayName":{"type":"string"},"handle":{"description":"Null for guests","type":["string","null"]}},"required":["playerId","displayName","handle"]},{"type":"null"}]},"decidedAt":{"type":["string","null"]}},"required":["id","status","source","side","atS","minute","clip","ownGoal","scorer","assist","confidence","options","decidedBy","decidedAt"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Organiser, side captains and admins (403 decideNotAllowed). confirm credits the goal (left-out fields keep the analysis's guess), reject drops it; a decided goal can be decided again to correct it. Players' goals and assists are recounted at once. 404 with fields.form goalGone when a new analysis replaced the candidate. 422 INVALID with fields: side (required), scorerId (required, notOnSide, notOpponent), assistId (notOnSide, sameAsScorer, ownGoal)."}},"/api/v1/players/{handle}":{"get":{"operationId":"getPlayersByHandle","summary":"A player's profile","tags":["Profiles"],"security":[],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"visible":{"type":"boolean","const":true},"isMe":{"type":"boolean"},"player":{"type":"object","properties":{"id":{"type":"string"},"handle":{"type":"string"},"displayName":{"type":"string"},"positions":{"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]}},"visibility":{"type":"string","enum":["public","teammates","private"]},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["id","handle","displayName","positions","visibility","photo"]},"teams":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"kitHex":{"type":["string","null"]},"role":{"type":"string","enum":["captain","co_captain","member"]},"canOpen":{"type":"boolean","description":"Whether you may open the team (GET /api/v1/teams/{slug}): members and admins only"}},"required":["slug","name","kitHex","role","canOpen"]}},"career":{"type":"object","properties":{"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Matches they played (attendance, or found in a published video analysis); cancelled ones do not count"},"wins":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"draws":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"losses":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"goals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Per match: the confirmed goals once the match has any (own goals left out); until then the goals they reported with the score"},"assists":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Assists on confirmed goals"},"motm":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Matches where nobody got more man-of-the-match votes"}},"required":["matches","wins","draws","losses","goals","assists","motm"]},"heatmap":{"anyOf":[{"type":"object","properties":{"cols":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rows":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"grid":{"type":"array","items":{"type":"number"},"description":"Seconds per cell, row by row; cols run along the pitch's length and the player attacks left to right"},"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["cols","rows","grid","matches"]},{"type":"null"}],"description":"The sum of their match heatmaps"},"attributes":{"anyOf":[{"type":"object","properties":{"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The analysed matches (tier A) behind the scores: the latest 10 at most"},"scores":{"type":"object","properties":{"pace":{"anyOf":[{"type":"integer","minimum":1,"maximum":99},{"type":"null"}],"description":"From their top speed per match (the median)"},"shooting":{"anyOf":[{"type":"integer","minimum":1,"maximum":99},{"type":"null"}],"description":"Goals per match (counted twice) and shots per ten minutes"},"passing":{"anyOf":[{"type":"integer","minimum":1,"maximum":99},{"type":"null"}],"description":"Pass accuracy (counted twice) and passes per ten minutes"},"involvement":{"anyOf":[{"type":"integer","minimum":1,"maximum":99},{"type":"null"}],"description":"Touches per ten minutes"},"stamina":{"anyOf":[{"type":"integer","minimum":1,"maximum":99},{"type":"null"}],"description":"Metres per minute, at a believable pace"}},"required":["pace","shooting","passing","involvement","stamina"]},"basis":{"type":"object","properties":{"goalsPerMatch":{"type":"number"},"shotsPer10":{"type":["number","null"]},"passAccuracy":{"description":"0–1","type":["number","null"]},"passesPer10":{"type":["number","null"]},"touchesPer10":{"type":["number","null"]},"metersPerMin":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Estimated"}},"required":["goalsPerMatch","shotsPer10","passAccuracy","passesPer10","touchesPer10","metersPerMin"]}},"required":["matches","scores","basis"],"description":"Attributes 1–99 worked out from the video analyses. With fewer than 3 matches behind a score it leans toward 50. A score is null when those matches hold nothing to judge it by."},{"type":"null"}],"description":"Null before their first analysed match with full numbers (tier A)"},"averages":{"anyOf":[{"type":"object","properties":{"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Every analysed match with full numbers (tier A)"},"rating":{"description":"Average match rating, 0–10","type":["number","null"]},"bestRating":{"type":["number","null"]},"minutes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"distanceKm":{"description":"Estimated, over matches at a believable pace","type":["number","null"]},"passes":{"type":["number","null"]},"passAccuracy":{"description":"0–1","type":["number","null"]},"touches":{"type":["number","null"]},"shots":{"type":["number","null"]}},"required":["matches","rating","bestRating","minutes","distanceKm","passes","passAccuracy","touches","shots"],"description":"Per-match averages from the video analyses"},{"type":"null"}],"description":"Null before their first analysed match with full numbers (tier A)"},"history":{"type":"array","items":{"type":"object","properties":{"matchId":{"type":"string"},"startsAt":{"type":"string","description":"ISO 8601"},"title":{"type":["string","null"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side they played on"},"sides":{"type":"array","items":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]}},"required":["side","label","goals"]}},"result":{"anyOf":[{"type":"string","enum":["win","draw","loss"]},{"type":"null"}]},"goals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Their goals: the confirmed ones once the match has any, until then the ones reported with the score"},"assists":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Their assists on confirmed goals"},"motm":{"type":"boolean","description":"Man of the match (most votes; shared tops count)"},"stat":{"anyOf":[{"type":"object","properties":{"tier":{"type":"string","enum":["A","B","C"],"description":"A: full stats; B: partial (minutes and heatmap); C: not enough data"},"minutes":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"distanceM":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Estimated; tier A at a believable pace only"},"rating":{"description":"The analysis's match rating, 0–10 to one decimal; tier A only","type":["number","null"]},"touches":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Tier A only"},"passes":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Tier A only"},"passesCompleted":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Tier A only"},"shots":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Tier A only"}},"required":["tier","minutes","distanceM","rating","touches","passes","passesCompleted","shots"]},{"type":"null"}]},"heatmap":{"anyOf":[{"type":"object","properties":{"cols":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rows":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"grid":{"type":"array","items":{"type":"number"},"description":"Seconds per cell, row by row; cols run along the pitch's length and the player attacks left to right"}},"required":["cols","rows","grid"]},{"type":"null"}],"description":"A coarse 16×10 grid for small drawings"},"canOpen":{"type":"boolean","description":"Whether you may open this match (GET its report); seeing a profile does not open its matches"}},"required":["matchId","startsAt","title","side","sides","result","goals","assists","motm","stat","heatmap","canOpen"]},"description":"The latest 30 matches, newest first"}},"required":["visible","isMe","player","teams","career","heatmap","attributes","averages","history"]},{"type":"object","properties":{"visible":{"type":"boolean","const":false},"isMe":{"type":"boolean","const":false},"player":{"type":"object","properties":{"handle":{"type":"string"},"displayName":{"type":"string"},"visibility":{"type":"string","enum":["teammates","private"]}},"required":["handle","displayName","visibility"]}},"required":["visible","isMe","player"]}]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Everyone sees the name and the handle. Positions, teams, career numbers, the heatmap and the match history follow the player's visibility: public to everyone; teammates to people who share a team with them, or a match they took part in (answered yes or maybe, or played); private to nobody else. When they are hidden the response is `{ visible: false, … }` with the name and the handle only. 404 NOT_FOUND for an unknown handle; 429 RATE_LIMITED after 30 unknown handles in 15 minutes (per account; signed out, per address, or in one count shared by every caller whose address cannot be trusted), for known handles too until the window ends."}},"/api/v1/me/photo":{"get":{"operationId":"getMePhoto","summary":"Your profile photo","tags":["Me"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["photo"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}},"put":{"operationId":"putMePhoto","summary":"Upload or replace your profile photo","tags":["Me"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["photo"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Send the photo as the body (`Content-Type: image/jpeg`, `image/png` or `image/webp`), or as `multipart/form-data` with the file in the `photo` field. Up to 10 MB, at least 256 px on its shorter side. The server turns it upright by its EXIF orientation, cuts the largest centred square (crop it yourself first to choose), and stores WebP copies without any metadata: location, camera and time are dropped. The photo is seen by whoever may see your stats (your visibility), and never used to recognise anyone in match videos. 422 INVALID with `fields.file` = required | type | tooBig | dimensions.","requestBody":{"required":true,"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"multipart/form-data":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"photo":{"type":"string","description":"The image file"}},"required":["photo"]}}}}},"delete":{"operationId":"deleteMePhoto","summary":"Remove your profile photo","tags":["Me"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]}},"required":["photo"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Your initials show again. Fine without a photo."}},"/api/v1/photos/{id}":{"get":{"operationId":"getPhotosById","summary":"A profile photo","tags":["Profiles"],"security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"size","in":"query","required":false,"schema":{"type":"string","enum":["small","medium","large"],"default":"medium"}}],"responses":{"200":{"description":"The image","content":{"image/webp":{"schema":{"type":"string","format":"binary"}}}},"304":{"description":"Not modified (send the ETag back in If-None-Match)"},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"A square WebP copy. Use the paths a player's `photo` gives you; a new upload gets a new id. 404 NOT_FOUND for an unknown id and for a photo you may not see (the player's visibility), alike. 422 INVALID for another size."}},"/api/v1/players/{handle}/card":{"get":{"operationId":"getPlayersByHandleCard","summary":"A player's card","tags":["Profiles"],"security":[],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"handle":{"type":"string"},"displayName":{"type":"string"},"positions":{"type":"array","items":{"type":"string","enum":["goalkeeper","defender","midfielder","forward"]}},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}]},"overall":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"1–99: ten times the average match rating (0–10, from the video analysis) of the latest 10 rated matches; null before the first"},"ratedMatches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many matches the overall averages"},"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"goals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"assists":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"motm":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Man of the match awards"},"winRate":{"description":"Wins as a share of matches with a result, 0–1","type":["number","null"]},"distanceKm":{"description":"Estimated kilometres per analysed match (tier A, at a believable pace)","type":["number","null"]},"passAccuracy":{"description":"Completed passes as a share of passes in analysed matches, 0–1","type":["number","null"]},"attributes":{"anyOf":[{"type":"object","properties":{"pace":{"anyOf":[{"type":"integer","minimum":1,"maximum":99},{"type":"null"}]},"shooting":{"anyOf":[{"type":"integer","minimum":1,"maximum":99},{"type":"null"}]},"passing":{"anyOf":[{"type":"integer","minimum":1,"maximum":99},{"type":"null"}]},"involvement":{"anyOf":[{"type":"integer","minimum":1,"maximum":99},{"type":"null"}]},"stamina":{"anyOf":[{"type":"integer","minimum":1,"maximum":99},{"type":"null"}]}},"required":["pace","shooting","passing","involvement","stamina"]},{"type":"null"}],"description":"The profile's attributes (pace, shooting, passing, involvement, stamina), 1–99, worked out from the video; null before the first analysed match with full numbers. With them the card shows these five and the matches instead of the six career numbers"},"heatmap":{"anyOf":[{"type":"object","properties":{"cols":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rows":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"grid":{"type":"array","items":{"type":"number"}}},"required":["cols","rows","grid"]},{"type":"null"}],"description":"The career heatmap: seconds per cell, cols along the length, attacking left to right"},"team":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"kitHex":{"type":["string","null"]}},"required":["name","kitHex"]},{"type":"null"}],"description":"The team on the card's badge: their first team by name"}},"required":["handle","displayName","positions","photo","overall","ratedMatches","matches","goals","assists","motm","winRate","distanceKm","passAccuracy","attributes","heatmap","team"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"The numbers on the player card: the same visibility as the profile (403 FORBIDDEN when the profile is hidden from you). 404 NOT_FOUND and 429 RATE_LIMITED as for GET /api/v1/players/{handle}."}},"/api/v1/players/{handle}/card/image":{"get":{"operationId":"getPlayersByHandleCardImage","summary":"A player's card as a PNG","tags":["Profiles"],"security":[],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}},{"name":"locale","in":"query","required":false,"schema":{"type":"string","enum":["tr","en"],"default":"tr"}},{"name":"download","in":"query","required":false,"schema":{"type":"string","enum":["1"]}}],"responses":{"200":{"description":"The image","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"304":{"description":"Not modified (send the ETag back in If-None-Match)"},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"1080 × 1920 PNG, the size WhatsApp status and Instagram stories fill, with the card clear of the top and bottom 250 px. Same visibility as the profile (403 FORBIDDEN when hidden from you). `download=1` answers with `Content-Disposition: attachment`. The answer carries an ETag; send it back in If-None-Match for a 304 while the card is unchanged. 429 RATE_LIMITED past 60 images in 10 minutes per person (signed out, per address)."}},"/api/v1/feed":{"get":{"operationId":"getFeed","summary":"The home screen: invitations, the next match, to-dos, recent results and teams","tags":["Feed"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"greeting":{"type":"object","properties":{"period":{"type":"string","enum":["morning","afternoon","evening","night"],"description":"The part of the day in Istanbul: 05–12 morning, 12–18 afternoon, 18–24 evening, 00–05 night"},"name":{"type":"string","description":"Your display name"}},"required":["period","name"]},"isNew":{"type":"boolean","description":"No teams and no matches yet: show a welcome with create a team, create a match and join with a code"},"invitations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The match id, for GET /api/v1/matches/{id}"},"title":{"description":"Null when untitled: name it by its sides, \"A – B\"","type":["string","null"]},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"ISO 8601 (UTC)"},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"label":{"type":"string"}},"required":["label"]},"B":{"type":"object","properties":{"label":{"type":"string"}},"required":["label"]}},"required":["A","B"],"description":"Side A wears orange in the apps, side B blue"},"venue":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"verified":{"type":"boolean","description":"Checked by an admin: show the verified badge"}},"required":["name","verified"]},{"type":"null"}]},"pitch":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]},{"type":"null"}]},"endsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"ISO 8601 (UTC)"},"organizer":{"type":"object","properties":{"displayName":{"type":"string"}},"required":["displayName"]},"team":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]},{"type":"null"}],"description":"Your team, when it was invited as a whole"},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side you would play on, when known"},"coming":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many said \"yes\""}},"required":["id","title","startsAt","sides","venue","pitch","endsAt","organizer","team","side","coming"]},"description":"Open matches waiting for your answer (POST /api/v1/matches/{id}/rsvp), soonest first; at most 20"},"nextMatch":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The match id, for GET /api/v1/matches/{id}"},"title":{"description":"Null when untitled: name it by its sides, \"A – B\"","type":["string","null"]},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"ISO 8601 (UTC)"},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"label":{"type":"string"}},"required":["label"]},"B":{"type":"object","properties":{"label":{"type":"string"}},"required":["label"]}},"required":["A","B"],"description":"Side A wears orange in the apps, side B blue"},"venue":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"},"verified":{"type":"boolean","description":"Checked by an admin: show the verified badge"}},"required":["name","verified"]},{"type":"null"}]},"pitch":{"anyOf":[{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]},{"type":"null"}]},"endsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"ISO 8601 (UTC)"},"kickedOff":{"type":"boolean","description":"The match is being played now"},"coming":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many said \"yes\""},"maybe":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many said \"maybe\""},"me":{"type":"object","properties":{"rsvp":{"type":"string","enum":["yes","maybe"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}]}},"required":["rsvp","side"]}},"required":["id","title","startsAt","sides","venue","pitch","endsAt","kickedOff","coming","maybe","me"]},{"type":"null"}],"description":"The soonest match you said \"yes\" or \"maybe\" to that is not over"},"todos":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["score","upload","prep","labels","goals","vote"],"description":"score: enter the score (organiser, side captains, admins; after kick-off). upload: upload the video (people who may upload, once the score is in, until a video arrives, unless the pitch's official camera records it). prep: mark the pitch (managers; analysis NEEDS_PREP). labels: say who is who (managers; NEEDS_LABELS). goals: confirm goal candidates (managers; once the analysis is PUBLISHED). vote: vote for the man of the match (people who played, when someone else played too; until seven days after kick-off, see voteUntil)"},"match":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$","description":"The match id, for GET /api/v1/matches/{id}"},"title":{"description":"Null when untitled: name it by its sides, \"A – B\"","type":["string","null"]},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"ISO 8601 (UTC)"},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"label":{"type":"string"}},"required":["label"]},"B":{"type":"object","properties":{"label":{"type":"string"}},"required":["label"]}},"required":["A","B"],"description":"Side A wears orange in the apps, side B blue"}},"required":["id","title","startsAt","sides"]},"candidates":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"goals: how many candidates wait for a decision; null for other kinds"},"voteUntil":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"ISO 8601 (UTC)"},{"type":"null"}],"description":"vote: when voting closes (kick-off plus seven days); null for other kinds"}},"required":["kind","match","candidates","voteUntil"]},"description":"One per pending action: the latest match first, in pipeline order within a match; from your 30 latest matches with work left"},"recent":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":["string","null"]},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$","description":"ISO 8601 (UTC)"},"sides":{"type":"object","properties":{"A":{"type":"object","properties":{"label":{"type":"string"},"goals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["label","goals"]},"B":{"type":"object","properties":{"label":{"type":"string"},"goals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["label","goals"]}},"required":["A","B"]},"side":{"anyOf":[{"type":"string","enum":["A","B"]},{"type":"null"}],"description":"The side you played on, when known"},"result":{"anyOf":[{"type":"string","enum":["win","draw","loss"]},{"type":"null"}],"description":"For your side; null when your side is unknown"},"rating":{"description":"0–10 with one decimal, from the published video analysis; null without one","type":["number","null"]}},"required":["id","title","startsAt","sides","side","result","rating"]},"description":"The last 5 matches you played that have a score, newest first"},"teams":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"slug":{"type":"string","description":"For GET /api/v1/teams/{slug}"},"name":{"type":"string"},"city":{"type":["string","null"]},"kitHomeHex":{"type":"string","pattern":"^#[0-9a-f]{6}$","description":"#rrggbb, lower-case"},"kitAwayHex":{"anyOf":[{"type":"string","pattern":"^#[0-9a-f]{6}$","description":"#rrggbb, lower-case"},{"type":"null"}]},"crest":{"anyOf":[{"type":"object","properties":{"shape":{"type":"string","enum":["shield","round","rounded","diamond","hexagon","pennant","arched","oval","crest","octagon","ticket","star"]},"primary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"secondary":{"type":"string","pattern":"^#[0-9a-f]{6}$"},"letter":{"type":"string"},"pattern":{"default":"plain","type":"string","enum":["plain","split","stripes","sash","chevron","hoops"]},"detail":{"default":"none","type":"string","enum":["none","star","ball","crown","laurel","wings"]}},"required":["shape","primary","secondary","letter"]},{"type":"null"}]},"role":{"type":"string","enum":["captain","co_captain","member"],"description":"Your role in the team"},"memberCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","slug","name","city","kitHomeHex","kitAwayHex","crest","role","memberCount"]},"description":"Your active teams, alphabetical"}},"required":["greeting","isNew","invitations","nextMatch","todos","recent","teams"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Everything the home screen shows, computed for the caller with the same rules as the match pages. Answer an invitation with POST /api/v1/matches/{id}/rsvp; each to-do names its match, and what to open follows from `kind`. The lists are capped, with no further pages: at most 20 invitations, to-dos from your 30 latest matches with work left, the last 5 results (GET /api/v1/matches lists more). Errors: 401 signed out."}},"/api/v1/pitch-admin/matches/{id}/uploads":{"post":{"operationId":"postPitchAdminMatchesByIdUploads","summary":"Start or resume delivering a match's recording","tags":["Pitch admin"],"security":[{"venueCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"fileName":{"type":"string","minLength":1,"maxLength":255,"description":"The picked file's name, e.g. mac.mp4. An extension other than a video container's (such as .m3u8) is refused"},"size":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"Bytes; at most 8 GiB"},"contentType":{"type":"string","description":"The file's MIME type; must be video/*"},"fingerprint":{"type":"string","minLength":1,"maxLength":512,"description":"name:size:lastModified of the file. Starting again with the same fingerprint resumes the unfinished upload"}},"required":["fileName","size","contentType","fingerprint"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"uploadId":{"type":"string"},"key":{"type":"string","description":"The object key in the bucket"},"partSize":{"type":"number","description":"Bytes per part (64 MiB); the last part holds the rest"},"partCount":{"type":"number"},"uploadedParts":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"number"},"etag":{"type":"string"},"size":{"type":"number"}},"required":["partNumber","etag","size"]},"description":"Parts the bucket already has (after a resume): upload only the others"},"resumed":{"type":"boolean","description":"True when this continues the venue's unfinished upload of the same file"}},"required":["uploadId","key","partSize","partCount","uploadedParts","resumed"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"The venue's recording goes straight to the bucket in parts, as people's uploads do (POST /api/v1/matches/{id}/uploads).\n\n1. POST here with the file's name, size, type and fingerprint (name:size:lastModified). The match becomes an official-camera match on its pitch's official camera, audited as venue.video.start.\n\n2. POST …/parts with the part numbers still missing and PUT each 64 MiB slice to its URL; keep the ETag response header.\n\n3. POST …/complete with every { partNumber, etag }: the analysis starts (INGESTING), audited as video.upload by the venue's login.\n\nStarting again with the same fingerprint resumes the venue's unfinished upload. Once a venue's upload has started, people's new uploads to the match are refused (409 venueDelivering).\n\n404 NOT_FOUND for a match that is not at the venue. 409 CONFLICT with fields.form matchCancelled, videoExists (the match has its video), uploadInProgress (someone else is uploading it), beforeKickoff (the recording is sent after kick-off) or noOfficialCamera (the match's pitch has no official camera). The score does not have to be in.\n\n422 INVALID with fields.size tooLarge (over 8 GiB), fields.contentType notVideo or fields.fileName notVideo.\n\nWeb only: signed in with the pitch admin panel's own session cookie (the akillifutbol-venue cookies set by /api/venue-auth/sign-in/username). Bearer tokens and people's sessions are not accepted (401). A login that has not finished its first sign-in's steps gets 403 with fields.form setup."}},"/api/v1/pitch-admin/matches/{id}/uploads/{uploadId}":{"delete":{"operationId":"deletePitchAdminMatchesByIdUploadsByUploadId","summary":"Cancel the venue's unfinished delivery","tags":["Pitch admin"],"security":[{"venueCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"uploadId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"uploadId":{"type":"string"},"status":{"type":"string","enum":["aborted","failed"]}},"required":["uploadId","status"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Drops its parts from the bucket. 409 uploadCompleted once it is complete (withdraw it from the panel instead, until labelling starts). Only the venue's own upload: anyone else's is not found (404). Web only: signed in with the pitch admin panel's own session cookie (the akillifutbol-venue cookies set by /api/venue-auth/sign-in/username). Bearer tokens and people's sessions are not accepted (401). A login that has not finished its first sign-in's steps gets 403 with fields.form setup."}},"/api/v1/pitch-admin/matches/{id}/uploads/{uploadId}/parts":{"post":{"operationId":"postPitchAdminMatchesByIdUploadsByUploadIdParts","summary":"Presigned URLs for parts of the venue's delivery","tags":["Pitch admin"],"security":[{"venueCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"uploadId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"partNumbers":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"integer","minimum":1,"maximum":10000},"description":"1-based part numbers, at most 100 per call"}},"required":["partNumbers"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"parts":{"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"number"},"url":{"type":"string","description":"PUT the part's bytes here, then read the ETag response header"}},"required":["partNumber","url"]}},"expiresAt":{"type":"string","description":"ISO time; sign again after it"}},"required":["parts","expiresAt"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Links are valid for an hour and accept exactly their part's bytes. 409 uploadClosed when the upload was cancelled or completed. The rules are checked again: 404 NOT_FOUND for a match that is not at the venue. 409 CONFLICT with fields.form matchCancelled, videoExists (the match has its video), uploadInProgress (someone else is uploading it), beforeKickoff (the recording is sent after kick-off) or noOfficialCamera (the match's pitch has no official camera). The score does not have to be in. Only the venue's own upload: anyone else's is not found (404). Web only: signed in with the pitch admin panel's own session cookie (the akillifutbol-venue cookies set by /api/venue-auth/sign-in/username). Bearer tokens and people's sessions are not accepted (401). A login that has not finished its first sign-in's steps gets 403 with fields.form setup."}},"/api/v1/pitch-admin/matches/{id}/uploads/{uploadId}/complete":{"post":{"operationId":"postPitchAdminMatchesByIdUploadsByUploadIdComplete","summary":"Finish the venue's delivery and start the analysis","tags":["Pitch admin"],"security":[{"venueCookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"uploadId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"parts":{"minItems":1,"maxItems":10000,"type":"array","items":{"type":"object","properties":{"partNumber":{"type":"integer","minimum":1,"maximum":10000},"etag":{"type":"string","minLength":1,"maxLength":200,"description":"The ETag header of the part's PUT response"}},"required":["partNumber","etag"]}}},"required":["parts"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"uploadId":{"type":"string"},"videoAssetId":{"type":["string","null"]},"analysisId":{"type":["string","null"]},"status":{"anyOf":[{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},{"type":"null"}]},"replaced":{"type":"boolean","description":"Always false: a venue never replaces a match's video"}},"required":["uploadId","videoAssetId","analysisId","status","replaced"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Checks the parts and the stored size, then creates the video, the analysis and its first job in one step; calling it again returns the same result. 409 partsMissing, partMismatch or sizeMismatch: upload the missing parts and complete again. 409 videoExists when another video became the match's first: this upload is then closed and its file deleted. Only the venue's own upload: anyone else's is not found (404). Web only: signed in with the pitch admin panel's own session cookie (the akillifutbol-venue cookies set by /api/venue-auth/sign-in/username). Bearer tokens and people's sessions are not accepted (401). A login that has not finished its first sign-in's steps gets 403 with fields.form setup."}},"/api/v1/admin/overview":{"get":{"operationId":"getAdminOverview","summary":"Headline numbers: people, teams, this week's matches, analyses by status","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"people":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"admins":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"banned":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"teams":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Not archived"},"matchesThisWeek":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Monday to Sunday in Turkish time, cancelled matches left out"},"week":{"type":"object","properties":{"start":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"end":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["start","end"]},"venues":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"unverifiedVenues":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"analyses":{"type":"object","propertyNames":{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},"additionalProperties":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"required":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},"awaitingRecordings":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Matches waiting for the pitch's official recording (see GET /api/v1/admin/official-recordings)"},"recentAudit":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"action":{"type":"string","description":"e.g. admin.ban, admin.venue.verify"},"targetType":{"type":"string"},"targetId":{"type":["string","null"]},"target":{"anyOf":[{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"person"},"name":{"type":"string"},"username":{"type":["string","null"]},"userId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["kind","name","username","userId"]},{"type":"object","properties":{"kind":{"type":"string","const":"team"},"name":{"type":"string"},"slug":{"type":"string"}},"required":["kind","name","slug"]},{"type":"object","properties":{"kind":{"type":"string","const":"match"},"name":{"type":"string"},"sides":{"type":"array","items":{"type":"string"}}},"required":["kind","name","sides"]},{"type":"object","properties":{"kind":{"type":"string","const":"venue"},"name":{"type":"string"}},"required":["kind","name"]}]},{"type":"null"}],"description":"The person, team, match or venue the entry is about, if it still exists"},"data":{"description":"Details of the change; never secrets"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"actorId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"Null for the system"},"actor":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"username":{"type":["string","null"]},"name":{"type":"string"},"userId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["id","username","name","userId"]},{"type":"null"}]}},"required":["id","action","targetType","targetId","target","data","createdAt","actorId","actor"]}}},"required":["people","admins","banned","teams","matchesThisWeek","week","venues","unverifiedVenues","analyses","awaitingRecordings","recentAudit"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/admin/people":{"get":{"operationId":"getAdminPeople","summary":"Find people by username or name","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"q","in":"query","required":false,"description":"Part of a username (a leading @ is ignored; Turkish letters match their plain forms) or a name; empty lists the newest accounts. An exact username comes first.","schema":{"type":"string"}},{"name":"filter","in":"query","required":false,"description":"admins or banned","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","enum":["admins","banned"]}},{"name":"limit","in":"query","required":false,"description":"1–100, default 30","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","default":30,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"username":{"type":["string","null"]},"name":{"type":"string","description":"The player's display name"},"role":{"type":"string","enum":["admin","user"]},"banned":{"type":"boolean"},"banReason":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"playerId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}],"description":"Their profile photo; admins see every photo"}},"required":["id","username","name","role","banned","banReason","createdAt","playerId","photo"]}}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/admin/people/{id}":{"get":{"operationId":"getAdminPeopleById","summary":"One person with their activity and recent audit entries","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"username":{"type":["string","null"]},"name":{"type":"string","description":"The player's display name"},"role":{"type":"string","enum":["admin","user"]},"banned":{"type":"boolean"},"banReason":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"playerId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}],"description":"Their profile photo; admins see every photo"},"sessions":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Open sessions"},"teams":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"audit":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"action":{"type":"string","description":"e.g. admin.ban, admin.venue.verify"},"targetType":{"type":"string"},"targetId":{"type":["string","null"]},"target":{"anyOf":[{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"person"},"name":{"type":"string"},"username":{"type":["string","null"]},"userId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["kind","name","username","userId"]},{"type":"object","properties":{"kind":{"type":"string","const":"team"},"name":{"type":"string"},"slug":{"type":"string"}},"required":["kind","name","slug"]},{"type":"object","properties":{"kind":{"type":"string","const":"match"},"name":{"type":"string"},"sides":{"type":"array","items":{"type":"string"}}},"required":["kind","name","sides"]},{"type":"object","properties":{"kind":{"type":"string","const":"venue"},"name":{"type":"string"}},"required":["kind","name"]}]},{"type":"null"}],"description":"The person, team, match or venue the entry is about, if it still exists"},"data":{"description":"Details of the change; never secrets"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"actorId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"Null for the system"},"actor":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"username":{"type":["string","null"]},"name":{"type":"string"},"userId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["id","username","name","userId"]},{"type":"null"}]}},"required":["id","action","targetType","targetId","target","data","createdAt","actorId","actor"]},"description":"The 20 newest entries by or about the person"}},"required":["id","username","name","role","banned","banReason","createdAt","playerId","photo","sessions","teams","matches","audit"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/admin/people/{id}/role":{"post":{"operationId":"postAdminPeopleByIdRole","summary":"Make someone an admin or take it away","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"role":{"type":"string","enum":["admin","user"],"description":"\"admin\" gives full access; \"user\" takes it away"}},"required":["role"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"username":{"type":["string","null"]},"name":{"type":"string","description":"The player's display name"},"role":{"type":"string","enum":["admin","user"]},"banned":{"type":"boolean"},"banReason":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"playerId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}],"description":"Their profile photo; admins see every photo"}},"required":["id","username","name","role","banned","banReason","createdAt","playerId","photo"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"409 CONFLICT with fields.form = \"self\" on your own account."}},"/api/v1/admin/people/{id}/ban":{"post":{"operationId":"postAdminPeopleByIdBan","summary":"Ban someone and end all their sessions","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"reason":{"type":"string","minLength":3,"maxLength":300,"description":"Shown to admins; 3–300 characters"}},"required":["reason"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"username":{"type":["string","null"]},"name":{"type":"string","description":"The player's display name"},"role":{"type":"string","enum":["admin","user"]},"banned":{"type":"boolean"},"banReason":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"playerId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}],"description":"Their profile photo; admins see every photo"}},"required":["id","username","name","role","banned","banReason","createdAt","playerId","photo"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Sign-in then answers 403 BANNED_USER. 409 CONFLICT with fields.form = \"self\" on your own account."}},"/api/v1/admin/people/{id}/unban":{"post":{"operationId":"postAdminPeopleByIdUnban","summary":"Lift a ban","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"username":{"type":["string","null"]},"name":{"type":"string","description":"The player's display name"},"role":{"type":"string","enum":["admin","user"]},"banned":{"type":"boolean"},"banReason":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"playerId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"photo":{"anyOf":[{"type":"object","properties":{"small":{"type":"string","description":"128 px"},"medium":{"type":"string","description":"512 px"},"large":{"type":"string","description":"1024 px"}},"required":["small","medium","large"],"description":"Square WebP copies of the player's profile photo: paths on this host (GET /api/v1/photos/{id}). Present only when you may see the player (their visibility, as for their stats); null shows their initials instead"},{"type":"null"}],"description":"Their profile photo; admins see every photo"}},"required":["id","username","name","role","banned","banReason","createdAt","playerId","photo"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/admin/people/{id}/reset-password":{"post":{"operationId":"postAdminPeopleByIdResetPassword","summary":"Set a temporary password and end all their sessions","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"password":{"type":"string","description":"Shown once, e.g. kavo-tesu-ribe-mena; only its hash is stored"}},"required":["password"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Pass the password on to the person; they can change it after signing in. 409 CONFLICT with fields.form = \"self\" on your own account."}},"/api/v1/admin/venues":{"get":{"operationId":"getAdminVenues","summary":"Venues, verified first","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"q","in":"query","required":false,"description":"Part of a name, city or district","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"pitches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cameras":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"officialCameras":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","name","city","district","address","verified","createdAt","matches","pitches","cameras","officialCameras"]}}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Verified venues first, then by name; at most 200, with no further pages: narrow the list with `q`."},"post":{"operationId":"postAdminVenues","summary":"Add a venue","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":80},"city":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}]},"district":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}]},"address":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"verified":{"default":false,"type":"boolean"}},"required":["name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"createdBy":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"displayName":{"type":"string"},"handle":{"type":["string","null"]}},"required":["id","displayName","handle"]},{"type":"null"}]},"pitches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"indoor":{"type":"boolean"},"lengthM":{"type":"number"},"widthM":{"type":"number"},"goalWidthM":{"type":"number"},"goalHeightM":{"type":"number"},"measured":{"type":"boolean","description":"False while the sizes are defaults"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cameras":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"label":{"type":"string"},"official":{"type":"boolean","description":"The venue's own recorder; at most one per pitch"},"activeCalibrationId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["id","label","official","activeCalibrationId","createdAt"]}}},"required":["id","name","indoor","lengthM","widthM","goalWidthM","goalHeightM","measured","createdAt","matches","cameras"]}}},"required":["id","name","city","district","address","verified","createdAt","matches","createdBy","pitches"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/admin/venues/{id}":{"get":{"operationId":"getAdminVenuesById","summary":"One venue with its pitches and cameras","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"createdBy":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"displayName":{"type":"string"},"handle":{"type":["string","null"]}},"required":["id","displayName","handle"]},{"type":"null"}]},"pitches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"indoor":{"type":"boolean"},"lengthM":{"type":"number"},"widthM":{"type":"number"},"goalWidthM":{"type":"number"},"goalHeightM":{"type":"number"},"measured":{"type":"boolean","description":"False while the sizes are defaults"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cameras":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"label":{"type":"string"},"official":{"type":"boolean","description":"The venue's own recorder; at most one per pitch"},"activeCalibrationId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["id","label","official","activeCalibrationId","createdAt"]}}},"required":["id","name","indoor","lengthM","widthM","goalWidthM","goalHeightM","measured","createdAt","matches","cameras"]}}},"required":["id","name","city","district","address","verified","createdAt","matches","createdBy","pitches"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}},"patch":{"operationId":"patchAdminVenuesById","summary":"Edit a venue, verify it or take the verification away","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":80},"city":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}]},"district":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}]},"address":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"verified":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"city":{"type":["string","null"]},"district":{"type":["string","null"]},"address":{"type":["string","null"]},"verified":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"createdBy":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"displayName":{"type":"string"},"handle":{"type":["string","null"]}},"required":["id","displayName","handle"]},{"type":"null"}]},"pitches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"indoor":{"type":"boolean"},"lengthM":{"type":"number"},"widthM":{"type":"number"},"goalWidthM":{"type":"number"},"goalHeightM":{"type":"number"},"measured":{"type":"boolean","description":"False while the sizes are defaults"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"matches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"cameras":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"label":{"type":"string"},"official":{"type":"boolean","description":"The venue's own recorder; at most one per pitch"},"activeCalibrationId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["id","label","official","activeCalibrationId","createdAt"]}}},"required":["id","name","indoor","lengthM","widthM","goalWidthM","goalHeightM","measured","createdAt","matches","cameras"]}}},"required":["id","name","city","district","address","verified","createdAt","matches","createdBy","pitches"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Send only the fields to change; an empty string or null clears city, district or address."}},"/api/v1/admin/venues/{id}/pitches":{"post":{"operationId":"postAdminVenuesByIdPitches","summary":"Add a pitch to a venue","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":40},"indoor":{"default":false,"type":"boolean"},"lengthM":{"default":40,"type":"number","minimum":15,"maximum":110},"widthM":{"default":25,"type":"number","minimum":10,"maximum":75},"goalWidthM":{"default":3,"type":"number","minimum":1,"maximum":7.32},"goalHeightM":{"default":2,"type":"number","minimum":1,"maximum":2.44},"measured":{"default":false,"type":"boolean"}},"required":["name"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"indoor":{"type":"boolean"},"lengthM":{"type":"number"},"widthM":{"type":"number"},"goalWidthM":{"type":"number"},"goalHeightM":{"type":"number"},"measured":{"type":"boolean","description":"False while the sizes are defaults"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"venueId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["id","name","indoor","lengthM","widthM","goalWidthM","goalHeightM","measured","createdAt","venueId"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Sizes are in metres. 409 CONFLICT with fields.name = \"taken\" when the venue has a pitch with that name; 422 with fields.widthM = \"widerThanLong\"."}},"/api/v1/admin/venues/{id}/login":{"get":{"operationId":"getAdminVenuesByIdLogin","summary":"The venue's pitch admin login, set-up checklist, booking health and recent changes","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"login":{"anyOf":[{"type":"object","properties":{"username":{"type":"string","description":"The pitch ID (\"Saha ID\"), e.g. saha_k7m3qp"},"enabled":{"type":"boolean","description":"False while switched off: sign-in is refused"},"ready":{"type":"boolean","description":"The venue set its own password and accepted the venue terms at its first sign-in"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"lastSignInAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}],"description":"The newest open session's start"}},"required":["username","enabled","ready","createdAt","lastSignInAt"]},{"type":"null"}]},"publicPath":{"description":"The venue's public page (/venues/<slug>) while its login is on","type":["string","null"]},"checklist":{"type":"array","items":{"type":"object","properties":{"item":{"type":"string","enum":["pitches","officialCameras","location","hours","login"]},"done":{"type":"boolean"}},"required":["item","done"]},"description":"Pitches added, an official camera on every pitch, location and opening hours set, the login on and its first sign-in done"},"health":{"type":"object","properties":{"reservationsEnabled":{"type":"boolean"},"openRequests":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Booking requests waiting for an answer that have not lapsed (a request lapses two hours before its start)"},"oldestPendingAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}],"description":"When the longest-waiting of them was asked"},"lastSignInAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}]}},"required":["reservationsEnabled","openRequests","oldestPendingAt","lastSignInAt"]},"audit":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"action":{"type":"string","description":"e.g. admin.ban, admin.venue.verify"},"targetType":{"type":"string"},"targetId":{"type":["string","null"]},"target":{"anyOf":[{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"person"},"name":{"type":"string"},"username":{"type":["string","null"]},"userId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["kind","name","username","userId"]},{"type":"object","properties":{"kind":{"type":"string","const":"team"},"name":{"type":"string"},"slug":{"type":"string"}},"required":["kind","name","slug"]},{"type":"object","properties":{"kind":{"type":"string","const":"match"},"name":{"type":"string"},"sides":{"type":"array","items":{"type":"string"}}},"required":["kind","name","sides"]},{"type":"object","properties":{"kind":{"type":"string","const":"venue"},"name":{"type":"string"}},"required":["kind","name"]}]},{"type":"null"}],"description":"The person, team, match or venue the entry is about, if it still exists"},"data":{"description":"Details of the change; never secrets"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"actorId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"Null for the system"},"actor":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"username":{"type":["string","null"]},"name":{"type":"string"},"userId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["id","username","name","userId"]},{"type":"null"}]}},"required":["id","action","targetType","targetId","target","data","createdAt","actorId","actor"]},"description":"The newest entries about the venue or by its login"}},"required":["login","publicPath","checklist","health","audit"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}},"post":{"operationId":"postAdminVenuesByIdLogin","summary":"Make the venue's pitch admin login","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"username":{"type":"string","description":"The pitch ID"},"password":{"type":"string","description":"Shown once, e.g. kavo-tesu-ribe-mena; only its hash is stored. It works for the first sign-in, where the venue sets its own"},"panelUrl":{"type":"string","description":"The panel's address, where the venue signs in"},"publicUrl":{"description":"The venue's public page","type":["string","null"]}},"required":["username","password","panelUrl","publicUrl"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Generates the pitch ID and a password, returned once (`Cache-Control: no-store`); pass them on to the venue with the panel's address. The venue gets its public address now if it has none. 409 CONFLICT with fields.form = \"loginExists\": a venue has one login."},"patch":{"operationId":"patchAdminVenuesByIdLogin","summary":"Switch the venue's login off or on","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"enabled":{"type":"boolean"}},"required":["enabled"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"username":{"type":"string","description":"The pitch ID (\"Saha ID\"), e.g. saha_k7m3qp"},"enabled":{"type":"boolean","description":"False while switched off: sign-in is refused"},"ready":{"type":"boolean","description":"The venue set its own password and accepted the venue terms at its first sign-in"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"lastSignInAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}],"description":"The newest open session's start"}},"required":["username","enabled","ready","createdAt","lastSignInAt"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Off is a ban: sign-in is refused and every session ends; the venue's bookings are switched off and its open requests declined with the answer \"@loginOff\". On again leaves bookings off until the venue switches them on. 404 when the venue has no login."}},"/api/v1/admin/venues/{id}/login/renew":{"post":{"operationId":"postAdminVenuesByIdLoginRenew","summary":"Give the venue's login a new password","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"username":{"type":"string","description":"The pitch ID"},"password":{"type":"string","description":"Shown once, e.g. kavo-tesu-ribe-mena; only its hash is stored. It works for the first sign-in, where the venue sets its own"},"panelUrl":{"type":"string","description":"The panel's address, where the venue signs in"},"publicUrl":{"description":"The venue's public page","type":["string","null"]}},"required":["username","password","panelUrl","publicUrl"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Returned once (`Cache-Control: no-store`). Every session of the login ends, and the venue sets its own password again at its next sign-in. 404 when the venue has no login."}},"/api/v1/admin/pitches/{id}":{"patch":{"operationId":"patchAdminPitchesById","summary":"Edit a pitch","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":40},"indoor":{"type":"boolean"},"lengthM":{"type":"number","minimum":15,"maximum":110},"widthM":{"type":"number","minimum":10,"maximum":75},"goalWidthM":{"type":"number","minimum":1,"maximum":7.32},"goalHeightM":{"type":"number","minimum":1,"maximum":2.44},"measured":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"indoor":{"type":"boolean"},"lengthM":{"type":"number"},"widthM":{"type":"number"},"goalWidthM":{"type":"number"},"goalHeightM":{"type":"number"},"measured":{"type":"boolean","description":"False while the sizes are defaults"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"venueId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["id","name","indoor","lengthM","widthM","goalWidthM","goalHeightM","measured","createdAt","venueId"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/admin/pitches/{id}/cameras":{"post":{"operationId":"postAdminPitchesByIdCameras","summary":"Add a camera to a pitch","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"label":{"type":"string","minLength":1,"maxLength":40},"official":{"default":false,"type":"boolean"}},"required":["label"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"label":{"type":"string"},"official":{"type":"boolean","description":"The venue's own recorder; at most one per pitch"},"activeCalibrationId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"pitchId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["id","label","official","activeCalibrationId","createdAt","pitchId"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Marking a camera official unmarks the pitch's previous official camera. 409 CONFLICT with fields.label = \"taken\"."}},"/api/v1/admin/cameras/{id}":{"patch":{"operationId":"patchAdminCamerasById","summary":"Rename a camera or mark it official","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"label":{"type":"string","minLength":1,"maxLength":40},"official":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"label":{"type":"string"},"official":{"type":"boolean","description":"The venue's own recorder; at most one per pitch"},"activeCalibrationId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"pitchId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["id","label","official","activeCalibrationId","createdAt","pitchId"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/admin/analyses":{"get":{"operationId":"getAdminAnalyses","summary":"Analyses with their match, error and jobs, most recently changed first","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"status","in":"query","required":false,"description":"INGESTING, NEEDS_PREP, TRACKING, NEEDS_LABELS, COMPUTING, PUBLISHED, FAILED","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]}},{"name":"limit","in":"query","required":false,"description":"1–100, default 50","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","default":50,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"analyses":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"status":{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},"progress":{"type":"number","description":"0–1 within the current stage"},"error":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"publishedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}]},"match":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":["string","null"]},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"status":{"type":"string","enum":["planned","played","cancelled"]},"sides":{"type":"array","items":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]}},"required":["side","label","goals"]}}},"required":["id","title","startsAt","status","sides"]},"jobs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"kind":{"type":"string","enum":["ingest","track","stats"]},"status":{"type":"string","enum":["queued","running","succeeded","failed"]},"attempts":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"maxAttempts":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"progress":{"type":"number"},"error":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"finishedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"null"}]}},"required":["id","kind","status","attempts","maxAttempts","progress","error","createdAt","finishedAt"]},"description":"Newest first"},"retryKind":{"anyOf":[{"type":"string","enum":["ingest","track","stats"]},{"type":"null"}],"description":"What POST …/retry would queue; null when there is nothing to retry"}},"required":["id","status","progress","error","createdAt","updatedAt","publishedAt","match","jobs","retryKind"]}},"counts":{"type":"object","propertyNames":{"type":"string","enum":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]},"additionalProperties":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"required":["INGESTING","NEEDS_PREP","TRACKING","NEEDS_LABELS","COMPUTING","PUBLISHED","FAILED"]}},"required":["analyses","counts"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/admin/official-recordings":{"get":{"operationId":"getAdminOfficialRecordings","summary":"Matches waiting for the pitch's official recording","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"matches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"title":{"type":["string","null"]},"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"durationMin":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"scoreState":{"type":"string","enum":["none","entered","confirmed"],"description":"The video can be uploaded once the score is in"},"sides":{"type":"array","items":{"type":"object","properties":{"side":{"type":"string","enum":["A","B"]},"label":{"type":"string"},"goals":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]}},"required":["side","label","goals"]}},"venue":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"verified":{"type":"boolean"}},"required":["id","name","verified"]},{"type":"null"}]},"pitch":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"}},"required":["id","name"]},{"type":"null"}]},"camera":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"label":{"type":"string"}},"required":["id","label"]},{"type":"null"}],"description":"The pitch's official camera that records the match"}},"required":["id","title","startsAt","durationMin","scoreState","sides","venue","pitch","camera"]},"description":"Oldest kick-off first, at most 50"},"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["matches","total"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Kick-off has passed, the match is not cancelled and no video has arrived. Admins deliver these recordings with the upload endpoints under Video, once the score is in."}},"/api/v1/admin/analyses/{id}/retry":{"post":{"operationId":"postAdminAnalysesByIdRetry","summary":"Queue the failed stage again","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"jobId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"kind":{"type":"string","enum":["ingest","track","stats"]}},"required":["jobId","kind"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}},"description":"Queues a new job of the newest job's kind with the same payload and moves the analysis back to that stage. 409 CONFLICT with fields.form = \"nothingToRetry\" when the newest job has not failed, and with fields.form = \"badPayload\" when the failed job's payload no longer matches the worker's job contract or names another analysis or video (POST /api/v1/matches/{id}/analysis/retry builds a fresh one)."}},"/api/v1/admin/audit":{"get":{"operationId":"getAdminAudit","summary":"The newest audit log entries","tags":["Admin"],"security":[{"bearer":[]},{"cookie":[]}],"parameters":[{"name":"action","in":"query","required":false,"description":"Only this action, e.g. admin.ban","schema":{"type":"string"}},{"name":"before","in":"query","required":false,"description":"The nextBefore of the previous page","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"limit","in":"query","required":false,"description":"1–200, default 50","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"action":{"type":"string","description":"e.g. admin.ban, admin.venue.verify"},"targetType":{"type":"string"},"targetId":{"type":["string","null"]},"target":{"anyOf":[{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"person"},"name":{"type":"string"},"username":{"type":["string","null"]},"userId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["kind","name","username","userId"]},{"type":"object","properties":{"kind":{"type":"string","const":"team"},"name":{"type":"string"},"slug":{"type":"string"}},"required":["kind","name","slug"]},{"type":"object","properties":{"kind":{"type":"string","const":"match"},"name":{"type":"string"},"sides":{"type":"array","items":{"type":"string"}}},"required":["kind","name","sides"]},{"type":"object","properties":{"kind":{"type":"string","const":"venue"},"name":{"type":"string"}},"required":["kind","name"]}]},{"type":"null"}],"description":"The person, team, match or venue the entry is about, if it still exists"},"data":{"description":"Details of the change; never secrets"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"actorId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}],"description":"Null for the system"},"actor":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"username":{"type":["string","null"]},"name":{"type":"string"},"userId":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]}},"required":["id","username","name","userId"]},{"type":"null"}]}},"required":["id","action","targetType","targetId","target","data","createdAt","actorId","actor"]}},"nextBefore":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"actions":{"type":"array","items":{"type":"string"},"description":"Every action in the log, for filtering"}},"required":["entries","nextBefore","actions"]}},"required":["data"]}}}},"4XX":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}},"5XX":{"description":"Server error (code INTERNAL)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}},"required":["code","message"]}},"required":["error"]}}}}}}}}}